From b00b677c54ee54af8640bf1d265c67963085848c Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Sun, 3 May 2026 16:47:53 -0600 Subject: [PATCH] Initial commit - Horux Despachos NL --- .gitattributes | 2 + .gitignore | 53 + .nvmrc | 1 + CLAUDE.md | 523 + .../9f877571-c527-445d-979a-9ab99479d851.pdf | Bin 0 -> 141156 bytes ...ZA640701TI9-tax-certificate-1767252856.pdf | Bin 0 -> 139478 bytes ...08138W2-tax-certificate-1767252858 (1).pdf | Bin 0 -> 139382 bytes ...BO230302410-tax-certificate-1767252857.pdf | Bin 0 -> 138058 bytes ...GC961208BXA-tax-certificate-1767252858.pdf | Bin 0 -> 139896 bytes ...DM9107165I0-tax-certificate-1767252858.pdf | Bin 0 -> 138169 bytes ...TS240708LJA-tax-certificate-1767252856.pdf | Bin 0 -> 139283 bytes ...TP020524UW0-tax-certificate-1767252857.pdf | Bin 0 -> 139111 bytes ...SJ870518SD7-tax-certificate-1767252856.pdf | Bin 0 -> 139208 bytes ...MC8311199VA-tax-certificate-1767252858.pdf | Bin 0 -> 138474 bytes ...RD791109L98-tax-certificate-1767252858.pdf | Bin 0 -> 138789 bytes ...RE790609168-tax-certificate-1767252857.pdf | Bin 0 -> 137806 bytes ...0201RA2-tax-certificate-1767252854 (1).pdf | Bin 0 -> 139874 bytes ...RA0007099R6-tax-certificate-1767252857.pdf | Bin 0 -> 140160 bytes ...RC9611214CA-tax-certificate-1767252856.pdf | Bin 0 -> 140875 bytes ...RS980325FH2-tax-certificate-1756664608.pdf | Bin 0 -> 143089 bytes ...PR840604D98-tax-certificate-1767252856.pdf | Bin 0 -> 140185 bytes README.md | 196 + apps/api/.env.example | 82 + apps/api/package.json | 65 + apps/api/prisma/catalogos-sat-data.ts | 121 + apps/api/prisma/eventos-fiscales-data.ts | 185 + apps/api/prisma/isr-data.ts | 103 + .../migration.sql | 634 + .../migration.sql | 47 + .../migration.sql | 16 + .../migration.sql | 35 + .../migration.sql | 28 + .../migration.sql | 19 + .../migration.sql | 2 + .../migration.sql | 10 + .../migration.sql | 18 + .../migration.sql | 2 + .../migration.sql | 18 + .../migration.sql | 19 + .../migration.sql | 55 + .../migration.sql | 5 + .../migration.sql | 10 + .../migration.sql | 7 + .../migration.sql | 5 + .../migration.sql | 5 + .../migration.sql | 8 + .../migration.sql | 4 + .../migration.sql | 6 + .../migration.sql | 6 + .../api/prisma/migrations/migration_lock.toml | 3 + apps/api/prisma/schema.prisma | 760 + apps/api/prisma/seed.ts | 528 + apps/api/scripts/apply-migration-042.ts | 37 + .../scripts/backfill-cfdi-contribuyente.ts | 158 + apps/api/scripts/backfill-cfdis-relaciones.ts | 209 + apps/api/scripts/backfill-facturapi-cfdis.ts | 126 + apps/api/scripts/backfill-fechas-tz.ts | 174 + apps/api/scripts/backfill-metricas.ts | 101 + apps/api/scripts/backfill-pago-fields.ts | 78 + apps/api/scripts/backfill-saldo-pendiente.ts | 163 + apps/api/scripts/bootstrap-horux360-admin.ts | 131 + apps/api/scripts/breakdown-gastos.ts | 75 + apps/api/scripts/breakdown-ingresos.ts | 67 + apps/api/scripts/check-cache-contrib.ts | 24 + apps/api/scripts/check-cache.ts | 26 + apps/api/scripts/check-carlos-emision.ts | 85 + apps/api/scripts/check-carlos-lco.ts | 72 + apps/api/scripts/check-ieps-inflation.ts | 112 + apps/api/scripts/check-recent-facturapi.ts | 76 + apps/api/scripts/check-rfc-emisor.ts | 36 + apps/api/scripts/check-saldo.ts | 63 + apps/api/scripts/compare-iva-full.ts | 37 + apps/api/scripts/compare-iva-gastos.ts | 36 + apps/api/scripts/count-07-types.ts | 22 + apps/api/scripts/count-husberto-07.ts | 27 + apps/api/scripts/create-carlos.ts | 26 + apps/api/scripts/debug-cfdi-activos.ts | 103 + apps/api/scripts/debug-compensacion-cfdi.ts | 154 + .../api/scripts/debug-deducciones-husberto.ts | 111 + apps/api/scripts/debug-drill-buckets.ts | 71 + apps/api/scripts/debug-i07-ppd.ts | 169 + apps/api/scripts/debug-i07.ts | 88 + .../scripts/debug-ingresos-horux-may-wider.ts | 104 + apps/api/scripts/debug-ingresos-horux-may.ts | 111 + apps/api/scripts/debug-ncs.ts | 34 + apps/api/scripts/debug-p-mayo.ts | 67 + apps/api/scripts/decrypt-fiel.ts | 82 + apps/api/scripts/deep-egresos.ts | 101 + apps/api/scripts/detail-ingresos.ts | 55 + apps/api/scripts/detail-iva-mes.ts | 68 + apps/api/scripts/drill-ingresos.ts | 88 + apps/api/scripts/extract-terminos.mjs | 79 + apps/api/scripts/find-contribuyente.ts | 28 + apps/api/scripts/find-i07-ppd-cases.ts | 75 + apps/api/scripts/find-uuid.ts | 12 + apps/api/scripts/import-lista-negra.ts | 104 + apps/api/scripts/inspect-cfdi-full.ts | 26 + apps/api/scripts/inspect-cfdi.ts | 90 + apps/api/scripts/inspect-facturapi-invoice.ts | 66 + apps/api/scripts/inspect-latest-facturapi.ts | 41 + apps/api/scripts/inspect-pair.ts | 52 + apps/api/scripts/inspect-rfc.ts | 48 + apps/api/scripts/invalidate-metricas-all.ts | 159 + apps/api/scripts/list-contribuyentes.ts | 26 + apps/api/scripts/migrate-tenants.ts | 33 + apps/api/scripts/otf-ingresos.ts | 27 + apps/api/scripts/preview-emails.mjs | 164 + apps/api/scripts/process-metricas-now.ts | 32 + apps/api/scripts/refresh-metricas-cache.ts | 59 + apps/api/scripts/set-horux-custom.ts | 100 + apps/api/scripts/setup-despachos-db.ts | 71 + apps/api/scripts/sweep-stale-sat-jobs.ts | 47 + apps/api/scripts/test-emails.ts | 96 + .../scripts/validate-dashboard-impuestos.ts | 97 + apps/api/scripts/validate-gastos.ts | 115 + apps/api/scripts/validate-ingresos.ts | 39 + apps/api/scripts/validate-metricas.ts | 160 + apps/api/src/app.ts | 112 + apps/api/src/auth/passwords.ts | 1 + apps/api/src/auth/tokens.ts | 30 + apps/api/src/config/database.ts | 234 + apps/api/src/config/env.ts | 89 + apps/api/src/config/tenant-migrations.ts | 143 + .../src/constants/obligaciones-fiscales.ts | 84 + .../controllers/activos-fijos.controller.ts | 87 + .../controllers/admin-addons.controller.ts | 86 + .../controllers/admin-clientes.controller.ts | 46 + .../controllers/admin-dashboard.controller.ts | 36 + .../admin-impersonate.controller.ts | 77 + .../api/src/controllers/alertas.controller.ts | 506 + .../src/controllers/audit-log.controller.ts | 87 + apps/api/src/controllers/auth.controller.ts | 203 + apps/api/src/controllers/bancos.controller.ts | 62 + .../src/controllers/calendario.controller.ts | 175 + .../api/src/controllers/cartera.controller.ts | 277 + .../src/controllers/catalogos.controller.ts | 108 + apps/api/src/controllers/cfdi.controller.ts | 530 + .../controllers/conciliacion.controller.ts | 58 + .../src/controllers/connector.controller.ts | 58 + .../contribuyente-config.controller.ts | 95 + .../controllers/contribuyente.controller.ts | 148 + .../src/controllers/dashboard.controller.ts | 108 + .../controllers/despacho-audit.controller.ts | 67 + .../controllers/despacho-stats.controller.ts | 67 + .../src/controllers/despacho.controller.ts | 98 + .../src/controllers/documentos.controller.ts | 333 + apps/api/src/controllers/export.controller.ts | 42 + .../src/controllers/facturacion.controller.ts | 789 + apps/api/src/controllers/fiel.controller.ts | 136 + .../src/controllers/impuestos.controller.ts | 171 + .../src/controllers/metricas.controller.ts | 25 + .../notification-preferences.controller.ts | 33 + .../controllers/obligaciones.controller.ts | 111 + .../src/controllers/papeleria.controller.ts | 263 + .../controllers/plan-catalogo.controller.ts | 92 + .../controllers/platform-staff.controller.ts | 187 + .../api/src/controllers/regimen.controller.ts | 70 + .../src/controllers/reportes.controller.ts | 85 + apps/api/src/controllers/sat.controller.ts | 168 + .../controllers/subscription.controller.ts | 359 + apps/api/src/controllers/tareas.controller.ts | 177 + .../api/src/controllers/tenants.controller.ts | 145 + .../src/controllers/usuarios.controller.ts | 273 + .../api/src/controllers/webhook.controller.ts | 244 + apps/api/src/index.ts | 57 + .../src/jobs/metricas-invalidations.job.ts | 53 + apps/api/src/jobs/notifications.job.ts | 104 + apps/api/src/jobs/sat-sync.job.ts | 501 + apps/api/src/jobs/weekly-update.job.ts | 154 + apps/api/src/middlewares/auth.middleware.ts | 112 + apps/api/src/middlewares/error.middleware.ts | 33 + .../middlewares/feature-gate.middleware.ts | 41 + .../src/middlewares/plan-limits.middleware.ts | 81 + .../src/middlewares/rate-limit.middleware.ts | 92 + apps/api/src/middlewares/tenant.middleware.ts | 144 + .../migrations/tenant/001_initial_schema.sql | 244 + .../002_create_opiniones_cumplimiento.sql | 16 + ...003_create_declaraciones_provisionales.sql | 36 + .../004_declaraciones_liga_pago_pdf.sql | 11 + ...05_create_constancias_situacion_fiscal.sql | 24 + .../tenant/006_tenant_migrations_tracking.sql | 17 + .../tenant/007_entidades_gestionadas.sql | 18 + .../src/migrations/tenant/008_carteras.sql | 27 + .../migrations/tenant/009_cliente_accesos.sql | 10 + .../migrations/tenant/010_contribuyentes.sql | 13 + .../tenant/011_fiel_per_contribuyente.sql | 23 + .../012_facturapi_per_contribuyente.sql | 11 + .../tenant/013_cfdi_contribuyente_id.sql | 7 + .../tenant/014_metricas_mensuales.sql | 52 + .../015_metricas_acumuladas_anuales.sql | 24 + .../016_metricas_por_contraparte_anuales.sql | 19 + .../tenant/017_metricas_invalidaciones.sql | 12 + .../tenant/018_obligaciones_contribuyente.sql | 20 + .../tenant/019_obligaciones_completada.sql | 8 + .../tenant/020_obligacion_periodos.sql | 17 + .../021_declaraciones_periodicidad_monto.sql | 15 + .../tenant/022_carteras_subcarteras.sql | 23 + .../tenant/023_bancos_contribuyente.sql | 4 + .../tenant/024_cfdi_descartados.sql | 12 + ...025_contribuyentes_regimen_fiscal_text.sql | 18 + .../tenant/026_normalize_cfdi_uuid_case.sql | 47 + .../027_cfdi_uuid_unique_case_insensitive.sql | 22 + .../tenant/028_documentos_extras.sql | 26 + ...030_obligacion_periodos_declaracion_id.sql | 17 + .../031_declaraciones_contribuyente_id.sql | 27 + .../tenant/032_cfdis_relaciones.sql | 22 + .../033_facturapi_orgs_lco_rejection.sql | 11 + .../034_contribuyentes_email_preferences.sql | 12 + apps/api/src/migrations/tenant/035_tareas.sql | 46 + .../tenant/036_papeleria_trabajo.sql | 38 + .../tenant/037_activos_fijos_baja.sql | 22 + .../038_activos_fijos_usos_excluidos.sql | 11 + .../tenant/039_alertas_notificadas.sql | 22 + .../tenant/040_recordatorios_email_notif.sql | 12 + .../tenant/041_facturapi_orgs_api_key_enc.sql | 13 + .../migrations/tenant/042_metricas_ncs.sql | 15 + .../tenant/043_metricas_no_deducibles.sql | 13 + apps/api/src/routes/admin-addons.routes.ts | 11 + apps/api/src/routes/admin-clientes.routes.ts | 11 + apps/api/src/routes/admin-dashboard.routes.ts | 12 + .../src/routes/admin-impersonate.routes.ts | 11 + apps/api/src/routes/alertas.routes.ts | 34 + apps/api/src/routes/audit-log.routes.ts | 13 + apps/api/src/routes/auth.routes.ts | 69 + apps/api/src/routes/bancos.routes.ts | 16 + apps/api/src/routes/calendario.routes.ts | 23 + apps/api/src/routes/cartera.routes.ts | 32 + apps/api/src/routes/catalogos.routes.ts | 21 + apps/api/src/routes/cfdi.routes.ts | 31 + apps/api/src/routes/conciliacion.routes.ts | 17 + apps/api/src/routes/connector.routes.ts | 15 + apps/api/src/routes/contribuyente.routes.ts | 47 + apps/api/src/routes/dashboard.routes.ts | 20 + apps/api/src/routes/despacho-audit.routes.ts | 12 + apps/api/src/routes/despacho-stats.routes.ts | 15 + apps/api/src/routes/despacho.routes.ts | 17 + apps/api/src/routes/documentos.routes.ts | 38 + apps/api/src/routes/export.routes.ts | 14 + apps/api/src/routes/facturacion.routes.ts | 64 + apps/api/src/routes/fiel.routes.ts | 20 + apps/api/src/routes/impuestos.routes.ts | 30 + apps/api/src/routes/metricas.routes.ts | 12 + .../routes/notification-preferences.routes.ts | 14 + apps/api/src/routes/papeleria.routes.ts | 18 + apps/api/src/routes/plan-catalogo.routes.ts | 17 + apps/api/src/routes/platform-staff.routes.ts | 16 + apps/api/src/routes/regimen.routes.ts | 28 + apps/api/src/routes/reportes.routes.ts | 24 + apps/api/src/routes/sat.routes.ts | 31 + apps/api/src/routes/subscription.routes.ts | 37 + apps/api/src/routes/tareas.routes.ts | 20 + apps/api/src/routes/tenants.routes.ts | 20 + apps/api/src/routes/usuarios.routes.ts | 29 + apps/api/src/routes/webhook.routes.ts | 9 + apps/api/src/services/_shared/cfdi-filters.ts | 148 + .../api/src/services/activos-fijos.service.ts | 265 + .../src/services/admin-clientes.service.ts | 147 + .../src/services/admin-dashboard.service.ts | 89 + apps/api/src/services/alertas-auto.service.ts | 683 + .../src/services/alertas-manuales.service.ts | 299 + apps/api/src/services/alertas.service.ts | 108 + apps/api/src/services/auth.service.ts | 653 + apps/api/src/services/bancos.service.ts | 63 + .../src/services/calendario-fiscal.service.ts | 271 + apps/api/src/services/cartera.service.ts | 160 + apps/api/src/services/cfdi.service.ts | 769 + apps/api/src/services/conciliacion.service.ts | 257 + apps/api/src/services/connector.service.ts | 156 + apps/api/src/services/constancia.service.ts | 402 + .../contribuyente-facturapi.service.ts | 493 + .../services/contribuyente-fiel.service.ts | 202 + .../api/src/services/contribuyente.service.ts | 187 + apps/api/src/services/dashboard.service.ts | 1253 ++ .../api/src/services/declaraciones.service.ts | 399 + .../src/services/despacho-stats.service.ts | 487 + apps/api/src/services/despacho.service.ts | 147 + .../src/services/documentos-extras.service.ts | 129 + apps/api/src/services/email/email.service.ts | 210 + .../email/templates/alertas-nuevas.ts | 90 + apps/api/src/services/email/templates/base.ts | 113 + .../email/templates/despacho-welcome.ts | 31 + .../email/templates/documento-subido.ts | 102 + .../email/templates/fiel-notification.ts | 17 + .../email/templates/new-client-admin.ts | 58 + .../src/services/email/templates/papeleria.ts | 57 + .../email/templates/password-reset.ts | 16 + .../email/templates/payment-confirmed.ts | 16 + .../email/templates/payment-failed.ts | 17 + .../email/templates/recordatorio-proximo.ts | 68 + .../email/templates/subscription-cancelled.ts | 15 + .../email/templates/subscription-expiring.ts | 14 + .../email/templates/tarea-completada.ts | 32 + .../email/templates/trial-reminder.ts | 58 + .../services/email/templates/weekly-update.ts | 122 + .../src/services/email/templates/welcome.ts | 17 + apps/api/src/services/export.service.ts | 125 + apps/api/src/services/facturapi.service.ts | 898 + apps/api/src/services/fiel.service.ts | 313 + apps/api/src/services/impuestos.service.ts | 1150 ++ apps/api/src/services/metabase.service.ts | 179 + .../src/services/metricas-compute.service.ts | 342 + apps/api/src/services/metricas.service.ts | 225 + .../notification-preferences.service.ts | 110 + .../api/src/services/notifications.service.ts | 390 + .../api/src/services/notify-upload.service.ts | 90 + apps/api/src/services/obligaciones.service.ts | 492 + .../services/opinion-cumplimiento.service.ts | 185 + apps/api/src/services/papeleria.service.ts | 211 + .../api/src/services/payment/addon.service.ts | 422 + .../src/services/payment/invoicing.service.ts | 351 + .../services/payment/mercadopago.service.ts | 340 + .../services/payment/subscription.service.ts | 1199 ++ .../api/src/services/plan-catalogo.service.ts | 210 + .../api/src/services/recordatorios.service.ts | 143 + apps/api/src/services/regimen.service.ts | 92 + apps/api/src/services/reportes.service.ts | 401 + apps/api/src/services/sat/sat-auth.service.ts | 160 + .../src/services/sat/sat-client.service.ts | 248 + .../src/services/sat/sat-crypto.service.ts | 94 + apps/api/src/services/sat/sat-csf-login.ts | 84 + apps/api/src/services/sat/sat-csf-parser.ts | 246 + apps/api/src/services/sat/sat-csf-scraper.ts | 121 + .../src/services/sat/sat-download.service.ts | 408 + .../api/src/services/sat/sat-opinion-login.ts | 92 + .../src/services/sat/sat-opinion-parser.ts | 76 + .../src/services/sat/sat-opinion-scraper.ts | 84 + .../src/services/sat/sat-parser.service.ts | 735 + apps/api/src/services/sat/sat.service.ts | 1463 ++ .../services/sat/sweep-stale-jobs.service.ts | 98 + apps/api/src/services/tareas.service.ts | 467 + apps/api/src/services/tenants.service.ts | 399 + apps/api/src/services/usuarios.service.ts | 251 + apps/api/src/utils/audit.ts | 72 + apps/api/src/utils/contribuyente-context.ts | 91 + apps/api/src/utils/entidades-visibles.ts | 68 + apps/api/src/utils/errors.ts | 10 + apps/api/src/utils/global-admin.ts | 14 + apps/api/src/utils/memberships.ts | 103 + apps/api/src/utils/metricas-cache.ts | 40 + apps/api/src/utils/platform-admin.ts | 133 + apps/api/src/utils/saldo.ts | 136 + apps/api/tsconfig.json | 18 + apps/web/.env.example | 1 + apps/web/app/(auth)/forgot-password/page.tsx | 118 + apps/web/app/(auth)/layout.tsx | 11 + apps/web/app/(auth)/login/page.tsx | 122 + .../web/app/(auth)/register-despacho/page.tsx | 415 + apps/web/app/(auth)/register/page.tsx | 145 + apps/web/app/(auth)/reset-password/page.tsx | 166 + .../app/(dashboard)/admin/audit-log/page.tsx | 229 + apps/web/app/(dashboard)/admin/staff/page.tsx | 264 + .../app/(dashboard)/admin/usuarios/page.tsx | 311 + .../cancelaciones-periodo-anterior/page.tsx | 126 + .../alertas/cancelaciones/page.tsx | 120 + .../alertas/concentracion-clientes/page.tsx | 79 + .../concentracion-proveedores/page.tsx | 79 + .../alertas/discrepancia-regimen/page.tsx | 344 + .../app/(dashboard)/alertas/efectivo/page.tsx | 118 + .../alertas/lista-negra-clientes/page.tsx | 63 + .../alertas/lista-negra-proveedores/page.tsx | 63 + apps/web/app/(dashboard)/alertas/page.tsx | 241 + .../alertas/tipo-relacion-sospechosa/page.tsx | 341 + apps/web/app/(dashboard)/calendario/page.tsx | 437 + apps/web/app/(dashboard)/carteras/page.tsx | 539 + apps/web/app/(dashboard)/cfdi/page.tsx | 2211 +++ apps/web/app/(dashboard)/clientes/page.tsx | 637 + .../web/app/(dashboard)/conciliacion/page.tsx | 411 + .../(dashboard)/configuracion/addons/page.tsx | 258 + .../(dashboard)/configuracion/csd/page.tsx | 374 + .../configuracion/facturacion/page.tsx | 222 + .../configuracion/notificaciones/page.tsx | 180 + .../configuracion/obligaciones/page.tsx | 499 + .../app/(dashboard)/configuracion/page.tsx | 647 + .../configuracion/planes-despacho/page.tsx | 483 + .../precios-suscripcion/page.tsx | 317 + .../configuracion/precios-timbres/page.tsx | 206 + .../(dashboard)/configuracion/sat/page.tsx | 197 + .../configuracion/seguridad/page.tsx | 166 + .../configuracion/suscripcion/page.tsx | 146 + .../contribuyentes/addons-dialog.tsx | 142 + .../app/(dashboard)/contribuyentes/page.tsx | 156 + apps/web/app/(dashboard)/dashboard/page.tsx | 418 + .../despachos/contribuyentes/page.tsx | 211 + .../app/(dashboard)/despachos/equipo/page.tsx | 273 + .../despachos/mis-asignados/page.tsx | 199 + apps/web/app/(dashboard)/despachos/page.tsx | 16 + apps/web/app/(dashboard)/documentos/page.tsx | 996 ++ apps/web/app/(dashboard)/drill-down/page.tsx | 179 + apps/web/app/(dashboard)/facturacion/page.tsx | 1621 ++ .../(dashboard)/facturacion/timbres/page.tsx | 219 + apps/web/app/(dashboard)/impuestos/page.tsx | 858 + apps/web/app/(dashboard)/layout.tsx | 88 + .../web/app/(dashboard)/mis-empresas/page.tsx | 275 + apps/web/app/(dashboard)/onboarding/page.tsx | 205 + apps/web/app/(dashboard)/pendientes/page.tsx | 466 + apps/web/app/(dashboard)/reportes/page.tsx | 442 + apps/web/app/(dashboard)/usuarios/page.tsx | 511 + apps/web/app/globals.css | 63 + apps/web/app/layout.tsx | 28 + apps/web/app/page.tsx | 5 + apps/web/app/terminos/page.tsx | 82 + apps/web/components/cfdi/cfdi-invoice.tsx | 324 + .../web/components/cfdi/cfdi-viewer-modal.tsx | 244 + apps/web/components/charts/bar-chart.tsx | 87 + apps/web/components/charts/index.ts | 1 + .../web/components/contribuyente-selector.tsx | 108 + .../components/despachos/despacho-subnav.tsx | 57 + .../components/documentos/papeleria-tab.tsx | 431 + apps/web/components/fiscal-disclaimer.tsx | 18 + .../impuestos/activos-fijos-tab.tsx | 369 + .../components/layouts/dashboard-shell.tsx | 18 + apps/web/components/layouts/header.tsx | 56 + .../components/layouts/sidebar-compact.tsx | 161 + .../components/layouts/sidebar-floating.tsx | 142 + apps/web/components/layouts/sidebar.tsx | 181 + apps/web/components/layouts/topnav.tsx | 144 + apps/web/components/membership-switcher.tsx | 114 + .../components/obligaciones/tareas-tab.tsx | 325 + .../onboarding/OnboardingScreen.tsx | 190 + apps/web/components/periodo-selector.tsx | 20 + .../components/providers/query-provider.tsx | 22 + .../components/providers/theme-provider.tsx | 26 + apps/web/components/sat/FielUploadModal.tsx | 138 + apps/web/components/sat/SyncHistory.tsx | 182 + apps/web/components/sat/SyncStatus.tsx | 251 + apps/web/components/subscription-banner.tsx | 155 + apps/web/components/tenant-selector.tsx | 158 + apps/web/content/terminos.ts | 300 + apps/web/lib/api/addons.ts | 37 + apps/web/lib/api/admin-clientes.ts | 44 + apps/web/lib/api/alertas.ts | 40 + apps/web/lib/api/audit-log.ts | 37 + apps/web/lib/api/auth.ts | 53 + apps/web/lib/api/bancos.ts | 28 + apps/web/lib/api/calendario.ts | 28 + apps/web/lib/api/carteras.ts | 73 + apps/web/lib/api/catalogos.ts | 24 + apps/web/lib/api/cfdi.ts | 198 + apps/web/lib/api/client.ts | 94 + apps/web/lib/api/conciliacion.ts | 58 + apps/web/lib/api/constancias.ts | 78 + apps/web/lib/api/contribuyentes.ts | 66 + apps/web/lib/api/dashboard.ts | 41 + apps/web/lib/api/declaraciones.ts | 87 + apps/web/lib/api/documentos.ts | 90 + apps/web/lib/api/facturacion.ts | 195 + apps/web/lib/api/fiel.ts | 41 + apps/web/lib/api/impuestos.ts | 76 + apps/web/lib/api/platform-staff.ts | 37 + apps/web/lib/api/reportes.ts | 69 + apps/web/lib/api/sat.ts | 50 + apps/web/lib/api/subscription.ts | 104 + apps/web/lib/api/tenants.ts | 97 + apps/web/lib/api/usuarios.ts | 36 + apps/web/lib/export-excel.ts | 30 + apps/web/lib/hooks/use-addons.ts | 29 + apps/web/lib/hooks/use-alertas.ts | 73 + apps/web/lib/hooks/use-audit-log.ts | 12 + apps/web/lib/hooks/use-bancos.ts | 39 + apps/web/lib/hooks/use-calendario.ts | 57 + apps/web/lib/hooks/use-carteras.ts | 62 + apps/web/lib/hooks/use-cfdi.ts | 72 + apps/web/lib/hooks/use-conciliacion.ts | 37 + apps/web/lib/hooks/use-constancias.ts | 42 + apps/web/lib/hooks/use-contribuyentes.ts | 48 + apps/web/lib/hooks/use-dashboard.ts | 47 + apps/web/lib/hooks/use-declaraciones.ts | 67 + apps/web/lib/hooks/use-documentos.ts | 41 + apps/web/lib/hooks/use-facturacion.ts | 53 + apps/web/lib/hooks/use-impuestos.ts | 65 + apps/web/lib/hooks/use-nav-gate.ts | 46 + apps/web/lib/hooks/use-platform-staff.ts | 40 + apps/web/lib/hooks/use-reportes.ts | 59 + apps/web/lib/hooks/use-subscription.ts | 161 + apps/web/lib/hooks/use-tenants.ts | 42 + apps/web/lib/hooks/use-usuarios.ts | 68 + apps/web/lib/onboarding.ts | 22 + apps/web/lib/utils.ts | 8 + apps/web/next-env.d.ts | 5 + apps/web/next.config.js | 36 + apps/web/package.json | 55 + apps/web/postcss.config.js | 6 + .../public/legal/terminos-y-condiciones.pdf | Bin 0 -> 118098 bytes apps/web/public/logo.jpg | Bin 0 -> 3575 bytes apps/web/stores/auth-store.ts | 46 + apps/web/stores/contribuyente-store.ts | 25 + apps/web/stores/periodo-store.ts | 45 + apps/web/stores/tenant-view-store.ts | 25 + apps/web/stores/theme-store.ts | 20 + apps/web/tailwind.config.ts | 67 + apps/web/themes/corporate.ts | 27 + apps/web/themes/dark.ts | 27 + apps/web/themes/index.ts | 17 + apps/web/themes/light.ts | 27 + apps/web/themes/vibrant.ts | 27 + apps/web/tsconfig.json | 21 + deploy/nginx/horux360.conf | 90 + deploy/systemd/horux-api.service | 17 + deploy/systemd/horux-web.service | 17 + docker-compose.yml | 53 + docs/Horux_despachos-vs-Horux360.md | 1682 ++ docs/SAT-SYNC-IMPLEMENTATION.md | 298 + docs/architecture/api-reference.md | 534 + docs/architecture/database-schema.prisma | 397 + docs/architecture/deployment.md | 250 + docs/legal/Terminos y condiciones.pdf | Bin 0 -> 118098 bytes docs/plans/2026-01-22-fase1-implementation.md | 2862 +++ docs/plans/2026-01-22-fase2-implementation.md | 2732 +++ docs/plans/2026-01-22-fase3-implementation.md | 2755 +++ docs/plans/2026-01-22-horux360-saas-design.md | 799 + docs/plans/2026-01-25-sat-sync-design.md | 327 + .../2026-01-25-sat-sync-implementation.md | 228 + docs/plans/2026-02-17-cfdi-viewer-design.md | 126 + .../2026-02-17-cfdi-viewer-implementation.md | 816 + .../2026-04-13-auto-invoicing-mp-payments.md | 174 + docs/plans/2026-04-13-cfdi-export-filters.md | 85 + .../2026-04-13-rol-admin-to-owner-rename.md | 163 + .../2026-04-13-sat-incremental-enterprise.md | 135 + .../2026-04-13-subscriptions-self-serve.md | 316 + .../2026-04-13-typescript-debt-cleanup.md | 99 + docs/plans/2026-04-14-audit-log.md | 237 + ...2026-04-14-documentos-csf-declaraciones.md | 108 + docs/plans/2026-04-14-jwt-revocation.md | 250 + ...026-04-14-owner-multi-rfc-subscriptions.md | 473 + docs/plans/2026-04-14-password-reset.md | 119 + docs/plans/2026-04-14-platform-admin-roles.md | 284 + docs/plans/2026-04-14-postgres-pitr.md | 161 + .../2026-04-14-rate-limiting-expansion.md | 295 + .../2026-04-14-reactivate-subscription.md | 95 + .../2026-04-14-trial-abuse-prevention.md | 126 + .../2026-04-18-session-fixes-and-features.md | 266 + docs/plans/2026-04-19-pending-features.md | 237 + ...2026-04-19-session-2-fixes-and-features.md | 184 + .../2026-04-21-facturacion-fixes-session.md | 339 + ...6-04-21-session-2-mp-setup-and-bugfixes.md | 1019 ++ docs/plans/2026-04-22-pendientes-y-addons.md | 391 + .../2026-04-23-features-fixes-y-derivados.md | 297 + .../2026-04-24-session-fixes-and-features.md | 861 + .../2026-04-25-despacho-tareas-papeleria.md | 513 + docs/plans/2026-04-26-admin-global-setup.md | 251 + docs/plans/2026-04-26-i07-ppd-compensacion.md | 491 + docs/plans/2026-04-26-iva-refactor.md | 193 + docs/plans/2026-04-26-notifications-email.md | 244 + .../2026-04-26-rebrand-planes-despacho.md | 82 + docs/plans/2026-04-26-session.md | 374 + docs/plans/2026-04-26-sprints-1-2-3.md | 320 + ...-27-i07-ppd-pue-only-y-cfdi-tipo-filter.md | 126 + docs/plans/2026-04-27-session.md | 631 + docs/plans/2026-04-30-session.md | 597 + docs/plans/2026-05-01-session.md | 1089 ++ docs/plans/2026-05-02-session.md | 1643 ++ .../2026-03-18-security-audit-remediation.md | 143 + docs/superpowers/INDEX.md | 116 + .../plans/2026-03-15-saas-transformation.md | 2252 +++ .../2026-04-12-conciliacion-implementation.md | 895 + .../plans/2026-04-13-opinion-cumplimiento.md | 1176 ++ .../plans/2026-04-13-tenant-migrations.md | 680 + .../2026-04-16-refactor-monorepo-packages.md | 2455 +++ .../plans/2026-04-17-plan2a-schema-auth.md | 772 + ...6-04-17-plan2b-contribuyentes-fiel-cfdi.md | 577 + ...lan2b2-fiel-facturapi-per-contribuyente.md | 29 + .../2026-04-17-plan2c-frontend-despachos.md | 789 + .../plans/2026-04-17-plan3-roles-carteras.md | 17 + ...-27-filtros-activos-ncs-impuestos-fase1.md | 946 + .../2026-04-27-isr-base-gravable-acumulada.md | 894 + .../2026-03-15-saas-transformation-design.md | 797 + .../specs/2026-04-12-conciliacion-design.md | 219 + .../specs/2026-04-12-sat-sync-chunking.md | 35 + .../2026-04-13-opinion-cumplimiento-design.md | 186 + .../2026-04-13-tenant-migrations-design.md | 106 + .../2026-04-16-horux-despachos-design.md | 1462 ++ .../specs/2026-04-27-custom-plan-design.md | 166 + ...26-04-27-drill-down-sort-by-name-design.md | 108 + ...tros-activos-ncs-impuestos-fase1-design.md | 347 + ...4-27-isr-base-gravable-acumulada-design.md | 298 + .../2026-04-27-trial-rfc-limit-design.md | 129 + ecosystem.config.js | 36 + .../Base gravable 605.png | Bin 0 -> 152059 bytes .../Base gravable 621.png | Bin 0 -> 154997 bytes .../Planes.registro 2.png | Bin 0 -> 151326 bytes .../Planes.registro 3.png | Bin 0 -> 215616 bytes errores_visuales_frontend/Planes.registro.png | Bin 0 -> 217391 bytes .../Recuperar contraseña.png | Bin 0 -> 58790 bytes errores_visuales_frontend/Reportes.png | Bin 0 -> 170460 bytes lista_negra/Listado_completo_69-B.csv | 14341 ++++++++++++++++ package.json | 26 + packages/core/package.json | 26 + packages/core/src/auth/index.ts | 2 + packages/core/src/auth/password.ts | 11 + packages/core/src/auth/token.ts | 37 + packages/core/src/crypto/aes-gcm.ts | 30 + packages/core/src/crypto/index.ts | 1 + packages/core/src/email/index.ts | 1 + packages/core/src/email/transport.ts | 60 + packages/core/src/index.ts | 3 + packages/core/tsconfig.json | 17 + packages/shared-ui/package.json | 33 + packages/shared-ui/src/charts/index.ts | 1 + packages/shared-ui/src/charts/kpi-card.tsx | 78 + packages/shared-ui/src/form/index.ts | 2 + .../shared-ui/src/form/period-selector.tsx | 235 + .../shared-ui/src/form/regimen-selector.tsx | 102 + packages/shared-ui/src/hooks/index.ts | 2 + packages/shared-ui/src/hooks/use-debounce.ts | 17 + .../shared-ui/src/hooks/use-table-sort.ts | 56 + packages/shared-ui/src/index.ts | 5 + packages/shared-ui/src/lib/cn.ts | 6 + packages/shared-ui/src/lib/index.ts | 1 + packages/shared-ui/src/primitives/button.tsx | 53 + packages/shared-ui/src/primitives/card.tsx | 78 + packages/shared-ui/src/primitives/dialog.tsx | 122 + packages/shared-ui/src/primitives/index.ts | 9 + packages/shared-ui/src/primitives/input.tsx | 24 + packages/shared-ui/src/primitives/label.tsx | 25 + packages/shared-ui/src/primitives/popover.tsx | 30 + packages/shared-ui/src/primitives/select.tsx | 99 + .../src/primitives/sortable-header.tsx | 26 + packages/shared-ui/src/primitives/tabs.tsx | 72 + packages/shared-ui/tailwind-preset.js | 51 + packages/shared-ui/tsconfig.json | 19 + packages/shared/package.json | 14 + .../shared/src/constants/despacho-plans.ts | 233 + packages/shared/src/constants/roles.ts | 76 + packages/shared/src/index.ts | 18 + packages/shared/src/types/alertas.ts | 35 + packages/shared/src/types/auth.ts | 89 + packages/shared/src/types/calendario.ts | 37 + packages/shared/src/types/cfdi.ts | 176 + packages/shared/src/types/dashboard.ts | 73 + packages/shared/src/types/despacho.ts | 44 + packages/shared/src/types/documentos.ts | 14 + packages/shared/src/types/impuestos.ts | 129 + packages/shared/src/types/reportes.ts | 46 + packages/shared/src/types/sat.ts | 132 + packages/shared/src/types/subscription.ts | 101 + packages/shared/src/types/tenant.ts | 44 + packages/shared/src/types/user.ts | 61 + packages/shared/tsconfig.json | 17 + packages/vertical-contable/package.json | 21 + packages/vertical-contable/src/index.ts | 1 + packages/vertical-contable/tsconfig.json | 17 + pnpm-lock.yaml | 5313 ++++++ pnpm-workspace.yaml | 3 + scripts/backup.sh | 71 + scripts/tune-postgres.sh | 27 + scripts/update-cfdi-xml.js | 135 + turbo.json | 31 + 647 files changed, 133843 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 CLAUDE.md create mode 100644 CSF_ejemplos/9f877571-c527-445d-979a-9ab99479d851.pdf create mode 100644 CSF_ejemplos/AUZA640701TI9-tax-certificate-1767252856.pdf create mode 100644 CSF_ejemplos/CAS2408138W2-tax-certificate-1767252858 (1).pdf create mode 100644 CSF_ejemplos/CBO230302410-tax-certificate-1767252857.pdf create mode 100644 CSF_ejemplos/FAGC961208BXA-tax-certificate-1767252858.pdf create mode 100644 CSF_ejemplos/GADM9107165I0-tax-certificate-1767252858.pdf create mode 100644 CSF_ejemplos/HTS240708LJA-tax-certificate-1767252856.pdf create mode 100644 CSF_ejemplos/ITP020524UW0-tax-certificate-1767252857.pdf create mode 100644 CSF_ejemplos/JISJ870518SD7-tax-certificate-1767252856.pdf create mode 100644 CSF_ejemplos/MOMC8311199VA-tax-certificate-1767252858.pdf create mode 100644 CSF_ejemplos/RORD791109L98-tax-certificate-1767252858.pdf create mode 100644 CSF_ejemplos/RORE790609168-tax-certificate-1767252857.pdf create mode 100644 CSF_ejemplos/TOAH680201RA2-tax-certificate-1767252854 (1).pdf create mode 100644 CSF_ejemplos/TORA0007099R6-tax-certificate-1767252857.pdf create mode 100644 CSF_ejemplos/TORC9611214CA-tax-certificate-1767252856.pdf create mode 100644 CSF_ejemplos/TORS980325FH2-tax-certificate-1756664608.pdf create mode 100644 CSF_ejemplos/TPR840604D98-tax-certificate-1767252856.pdf create mode 100644 README.md create mode 100644 apps/api/.env.example create mode 100644 apps/api/package.json create mode 100644 apps/api/prisma/catalogos-sat-data.ts create mode 100644 apps/api/prisma/eventos-fiscales-data.ts create mode 100644 apps/api/prisma/isr-data.ts create mode 100644 apps/api/prisma/migrations/20260414152220_initial_schema_v0_9_2/migration.sql create mode 100644 apps/api/prisma/migrations/20260415000057_timbres_paquetes_adicionales/migration.sql create mode 100644 apps/api/prisma/migrations/20260417204528_despacho_fields/migration.sql create mode 100644 apps/api/prisma/migrations/20260417224212_plan_catalogo_tables/migration.sql create mode 100644 apps/api/prisma/migrations/20260417224614_subscription_addons/migration.sql create mode 100644 apps/api/prisma/migrations/20260417225702_connector_heartbeats/migration.sql create mode 100644 apps/api/prisma/migrations/20260418165004_sat_sync_contribuyente_id/migration.sql create mode 100644 apps/api/prisma/migrations/20260421062505_despacho_plan_enum_values/migration.sql create mode 100644 apps/api/prisma/migrations/20260422172323_subscription_addons_contribuyente_id/migration.sql create mode 100644 apps/api/prisma/migrations/20260426073942_add_mi_empresa_plan/migration.sql create mode 100644 apps/api/prisma/migrations/20260426230000_despacho_plan_prices/migration.sql create mode 100644 apps/api/prisma/migrations/20260430184123_cleanup_legacy_plans/migration.sql create mode 100644 apps/api/prisma/migrations/20260430195000_extend_despacho_plan_prices_with_limits/migration.sql create mode 100644 apps/api/prisma/migrations/20260430200000_drop_plan_catalogo_orphan/migration.sql create mode 100644 apps/api/prisma/migrations/20260430215000_add_permite_sat_incremental/migration.sql create mode 100644 apps/api/prisma/migrations/20260430230000_add_facturapi_org_key_enc/migration.sql create mode 100644 apps/api/prisma/migrations/20260501160000_drop_plan_prices_legacy/migration.sql create mode 100644 apps/api/prisma/migrations/20260501170000_add_subscription_reminder_tracking/migration.sql create mode 100644 apps/api/prisma/migrations/20260502170000_add_tenant_fact_preferencias/migration.sql create mode 100644 apps/api/prisma/migrations/20260502190000_add_user_login_count_onboarding_dismissed/migration.sql create mode 100644 apps/api/prisma/migrations/20260502210000_add_sat_sync_jobs_request_ids_map/migration.sql create mode 100644 apps/api/prisma/migrations/20260502230000_add_sat_sync_jobs_is_custom_range/migration.sql create mode 100644 apps/api/prisma/migrations/migration_lock.toml create mode 100644 apps/api/prisma/schema.prisma create mode 100644 apps/api/prisma/seed.ts create mode 100644 apps/api/scripts/apply-migration-042.ts create mode 100644 apps/api/scripts/backfill-cfdi-contribuyente.ts create mode 100644 apps/api/scripts/backfill-cfdis-relaciones.ts create mode 100644 apps/api/scripts/backfill-facturapi-cfdis.ts create mode 100644 apps/api/scripts/backfill-fechas-tz.ts create mode 100644 apps/api/scripts/backfill-metricas.ts create mode 100644 apps/api/scripts/backfill-pago-fields.ts create mode 100644 apps/api/scripts/backfill-saldo-pendiente.ts create mode 100644 apps/api/scripts/bootstrap-horux360-admin.ts create mode 100644 apps/api/scripts/breakdown-gastos.ts create mode 100644 apps/api/scripts/breakdown-ingresos.ts create mode 100644 apps/api/scripts/check-cache-contrib.ts create mode 100644 apps/api/scripts/check-cache.ts create mode 100644 apps/api/scripts/check-carlos-emision.ts create mode 100644 apps/api/scripts/check-carlos-lco.ts create mode 100644 apps/api/scripts/check-ieps-inflation.ts create mode 100644 apps/api/scripts/check-recent-facturapi.ts create mode 100644 apps/api/scripts/check-rfc-emisor.ts create mode 100644 apps/api/scripts/check-saldo.ts create mode 100644 apps/api/scripts/compare-iva-full.ts create mode 100644 apps/api/scripts/compare-iva-gastos.ts create mode 100644 apps/api/scripts/count-07-types.ts create mode 100644 apps/api/scripts/count-husberto-07.ts create mode 100644 apps/api/scripts/create-carlos.ts create mode 100644 apps/api/scripts/debug-cfdi-activos.ts create mode 100644 apps/api/scripts/debug-compensacion-cfdi.ts create mode 100644 apps/api/scripts/debug-deducciones-husberto.ts create mode 100644 apps/api/scripts/debug-drill-buckets.ts create mode 100644 apps/api/scripts/debug-i07-ppd.ts create mode 100644 apps/api/scripts/debug-i07.ts create mode 100644 apps/api/scripts/debug-ingresos-horux-may-wider.ts create mode 100644 apps/api/scripts/debug-ingresos-horux-may.ts create mode 100644 apps/api/scripts/debug-ncs.ts create mode 100644 apps/api/scripts/debug-p-mayo.ts create mode 100644 apps/api/scripts/decrypt-fiel.ts create mode 100644 apps/api/scripts/deep-egresos.ts create mode 100644 apps/api/scripts/detail-ingresos.ts create mode 100644 apps/api/scripts/detail-iva-mes.ts create mode 100644 apps/api/scripts/drill-ingresos.ts create mode 100644 apps/api/scripts/extract-terminos.mjs create mode 100644 apps/api/scripts/find-contribuyente.ts create mode 100644 apps/api/scripts/find-i07-ppd-cases.ts create mode 100644 apps/api/scripts/find-uuid.ts create mode 100644 apps/api/scripts/import-lista-negra.ts create mode 100644 apps/api/scripts/inspect-cfdi-full.ts create mode 100644 apps/api/scripts/inspect-cfdi.ts create mode 100644 apps/api/scripts/inspect-facturapi-invoice.ts create mode 100644 apps/api/scripts/inspect-latest-facturapi.ts create mode 100644 apps/api/scripts/inspect-pair.ts create mode 100644 apps/api/scripts/inspect-rfc.ts create mode 100644 apps/api/scripts/invalidate-metricas-all.ts create mode 100644 apps/api/scripts/list-contribuyentes.ts create mode 100644 apps/api/scripts/migrate-tenants.ts create mode 100644 apps/api/scripts/otf-ingresos.ts create mode 100644 apps/api/scripts/preview-emails.mjs create mode 100644 apps/api/scripts/process-metricas-now.ts create mode 100644 apps/api/scripts/refresh-metricas-cache.ts create mode 100644 apps/api/scripts/set-horux-custom.ts create mode 100644 apps/api/scripts/setup-despachos-db.ts create mode 100644 apps/api/scripts/sweep-stale-sat-jobs.ts create mode 100644 apps/api/scripts/test-emails.ts create mode 100644 apps/api/scripts/validate-dashboard-impuestos.ts create mode 100644 apps/api/scripts/validate-gastos.ts create mode 100644 apps/api/scripts/validate-ingresos.ts create mode 100644 apps/api/scripts/validate-metricas.ts create mode 100644 apps/api/src/app.ts create mode 100644 apps/api/src/auth/passwords.ts create mode 100644 apps/api/src/auth/tokens.ts create mode 100644 apps/api/src/config/database.ts create mode 100644 apps/api/src/config/env.ts create mode 100644 apps/api/src/config/tenant-migrations.ts create mode 100644 apps/api/src/constants/obligaciones-fiscales.ts create mode 100644 apps/api/src/controllers/activos-fijos.controller.ts create mode 100644 apps/api/src/controllers/admin-addons.controller.ts create mode 100644 apps/api/src/controllers/admin-clientes.controller.ts create mode 100644 apps/api/src/controllers/admin-dashboard.controller.ts create mode 100644 apps/api/src/controllers/admin-impersonate.controller.ts create mode 100644 apps/api/src/controllers/alertas.controller.ts create mode 100644 apps/api/src/controllers/audit-log.controller.ts create mode 100644 apps/api/src/controllers/auth.controller.ts create mode 100644 apps/api/src/controllers/bancos.controller.ts create mode 100644 apps/api/src/controllers/calendario.controller.ts create mode 100644 apps/api/src/controllers/cartera.controller.ts create mode 100644 apps/api/src/controllers/catalogos.controller.ts create mode 100644 apps/api/src/controllers/cfdi.controller.ts create mode 100644 apps/api/src/controllers/conciliacion.controller.ts create mode 100644 apps/api/src/controllers/connector.controller.ts create mode 100644 apps/api/src/controllers/contribuyente-config.controller.ts create mode 100644 apps/api/src/controllers/contribuyente.controller.ts create mode 100644 apps/api/src/controllers/dashboard.controller.ts create mode 100644 apps/api/src/controllers/despacho-audit.controller.ts create mode 100644 apps/api/src/controllers/despacho-stats.controller.ts create mode 100644 apps/api/src/controllers/despacho.controller.ts create mode 100644 apps/api/src/controllers/documentos.controller.ts create mode 100644 apps/api/src/controllers/export.controller.ts create mode 100644 apps/api/src/controllers/facturacion.controller.ts create mode 100644 apps/api/src/controllers/fiel.controller.ts create mode 100644 apps/api/src/controllers/impuestos.controller.ts create mode 100644 apps/api/src/controllers/metricas.controller.ts create mode 100644 apps/api/src/controllers/notification-preferences.controller.ts create mode 100644 apps/api/src/controllers/obligaciones.controller.ts create mode 100644 apps/api/src/controllers/papeleria.controller.ts create mode 100644 apps/api/src/controllers/plan-catalogo.controller.ts create mode 100644 apps/api/src/controllers/platform-staff.controller.ts create mode 100644 apps/api/src/controllers/regimen.controller.ts create mode 100644 apps/api/src/controllers/reportes.controller.ts create mode 100644 apps/api/src/controllers/sat.controller.ts create mode 100644 apps/api/src/controllers/subscription.controller.ts create mode 100644 apps/api/src/controllers/tareas.controller.ts create mode 100644 apps/api/src/controllers/tenants.controller.ts create mode 100644 apps/api/src/controllers/usuarios.controller.ts create mode 100644 apps/api/src/controllers/webhook.controller.ts create mode 100644 apps/api/src/index.ts create mode 100644 apps/api/src/jobs/metricas-invalidations.job.ts create mode 100644 apps/api/src/jobs/notifications.job.ts create mode 100644 apps/api/src/jobs/sat-sync.job.ts create mode 100644 apps/api/src/jobs/weekly-update.job.ts create mode 100644 apps/api/src/middlewares/auth.middleware.ts create mode 100644 apps/api/src/middlewares/error.middleware.ts create mode 100644 apps/api/src/middlewares/feature-gate.middleware.ts create mode 100644 apps/api/src/middlewares/plan-limits.middleware.ts create mode 100644 apps/api/src/middlewares/rate-limit.middleware.ts create mode 100644 apps/api/src/middlewares/tenant.middleware.ts create mode 100644 apps/api/src/migrations/tenant/001_initial_schema.sql create mode 100644 apps/api/src/migrations/tenant/002_create_opiniones_cumplimiento.sql create mode 100644 apps/api/src/migrations/tenant/003_create_declaraciones_provisionales.sql create mode 100644 apps/api/src/migrations/tenant/004_declaraciones_liga_pago_pdf.sql create mode 100644 apps/api/src/migrations/tenant/005_create_constancias_situacion_fiscal.sql create mode 100644 apps/api/src/migrations/tenant/006_tenant_migrations_tracking.sql create mode 100644 apps/api/src/migrations/tenant/007_entidades_gestionadas.sql create mode 100644 apps/api/src/migrations/tenant/008_carteras.sql create mode 100644 apps/api/src/migrations/tenant/009_cliente_accesos.sql create mode 100644 apps/api/src/migrations/tenant/010_contribuyentes.sql create mode 100644 apps/api/src/migrations/tenant/011_fiel_per_contribuyente.sql create mode 100644 apps/api/src/migrations/tenant/012_facturapi_per_contribuyente.sql create mode 100644 apps/api/src/migrations/tenant/013_cfdi_contribuyente_id.sql create mode 100644 apps/api/src/migrations/tenant/014_metricas_mensuales.sql create mode 100644 apps/api/src/migrations/tenant/015_metricas_acumuladas_anuales.sql create mode 100644 apps/api/src/migrations/tenant/016_metricas_por_contraparte_anuales.sql create mode 100644 apps/api/src/migrations/tenant/017_metricas_invalidaciones.sql create mode 100644 apps/api/src/migrations/tenant/018_obligaciones_contribuyente.sql create mode 100644 apps/api/src/migrations/tenant/019_obligaciones_completada.sql create mode 100644 apps/api/src/migrations/tenant/020_obligacion_periodos.sql create mode 100644 apps/api/src/migrations/tenant/021_declaraciones_periodicidad_monto.sql create mode 100644 apps/api/src/migrations/tenant/022_carteras_subcarteras.sql create mode 100644 apps/api/src/migrations/tenant/023_bancos_contribuyente.sql create mode 100644 apps/api/src/migrations/tenant/024_cfdi_descartados.sql create mode 100644 apps/api/src/migrations/tenant/025_contribuyentes_regimen_fiscal_text.sql create mode 100644 apps/api/src/migrations/tenant/026_normalize_cfdi_uuid_case.sql create mode 100644 apps/api/src/migrations/tenant/027_cfdi_uuid_unique_case_insensitive.sql create mode 100644 apps/api/src/migrations/tenant/028_documentos_extras.sql create mode 100644 apps/api/src/migrations/tenant/030_obligacion_periodos_declaracion_id.sql create mode 100644 apps/api/src/migrations/tenant/031_declaraciones_contribuyente_id.sql create mode 100644 apps/api/src/migrations/tenant/032_cfdis_relaciones.sql create mode 100644 apps/api/src/migrations/tenant/033_facturapi_orgs_lco_rejection.sql create mode 100644 apps/api/src/migrations/tenant/034_contribuyentes_email_preferences.sql create mode 100644 apps/api/src/migrations/tenant/035_tareas.sql create mode 100644 apps/api/src/migrations/tenant/036_papeleria_trabajo.sql create mode 100644 apps/api/src/migrations/tenant/037_activos_fijos_baja.sql create mode 100644 apps/api/src/migrations/tenant/038_activos_fijos_usos_excluidos.sql create mode 100644 apps/api/src/migrations/tenant/039_alertas_notificadas.sql create mode 100644 apps/api/src/migrations/tenant/040_recordatorios_email_notif.sql create mode 100644 apps/api/src/migrations/tenant/041_facturapi_orgs_api_key_enc.sql create mode 100644 apps/api/src/migrations/tenant/042_metricas_ncs.sql create mode 100644 apps/api/src/migrations/tenant/043_metricas_no_deducibles.sql create mode 100644 apps/api/src/routes/admin-addons.routes.ts create mode 100644 apps/api/src/routes/admin-clientes.routes.ts create mode 100644 apps/api/src/routes/admin-dashboard.routes.ts create mode 100644 apps/api/src/routes/admin-impersonate.routes.ts create mode 100644 apps/api/src/routes/alertas.routes.ts create mode 100644 apps/api/src/routes/audit-log.routes.ts create mode 100644 apps/api/src/routes/auth.routes.ts create mode 100644 apps/api/src/routes/bancos.routes.ts create mode 100644 apps/api/src/routes/calendario.routes.ts create mode 100644 apps/api/src/routes/cartera.routes.ts create mode 100644 apps/api/src/routes/catalogos.routes.ts create mode 100644 apps/api/src/routes/cfdi.routes.ts create mode 100644 apps/api/src/routes/conciliacion.routes.ts create mode 100644 apps/api/src/routes/connector.routes.ts create mode 100644 apps/api/src/routes/contribuyente.routes.ts create mode 100644 apps/api/src/routes/dashboard.routes.ts create mode 100644 apps/api/src/routes/despacho-audit.routes.ts create mode 100644 apps/api/src/routes/despacho-stats.routes.ts create mode 100644 apps/api/src/routes/despacho.routes.ts create mode 100644 apps/api/src/routes/documentos.routes.ts create mode 100644 apps/api/src/routes/export.routes.ts create mode 100644 apps/api/src/routes/facturacion.routes.ts create mode 100644 apps/api/src/routes/fiel.routes.ts create mode 100644 apps/api/src/routes/impuestos.routes.ts create mode 100644 apps/api/src/routes/metricas.routes.ts create mode 100644 apps/api/src/routes/notification-preferences.routes.ts create mode 100644 apps/api/src/routes/papeleria.routes.ts create mode 100644 apps/api/src/routes/plan-catalogo.routes.ts create mode 100644 apps/api/src/routes/platform-staff.routes.ts create mode 100644 apps/api/src/routes/regimen.routes.ts create mode 100644 apps/api/src/routes/reportes.routes.ts create mode 100644 apps/api/src/routes/sat.routes.ts create mode 100644 apps/api/src/routes/subscription.routes.ts create mode 100644 apps/api/src/routes/tareas.routes.ts create mode 100644 apps/api/src/routes/tenants.routes.ts create mode 100644 apps/api/src/routes/usuarios.routes.ts create mode 100644 apps/api/src/routes/webhook.routes.ts create mode 100644 apps/api/src/services/_shared/cfdi-filters.ts create mode 100644 apps/api/src/services/activos-fijos.service.ts create mode 100644 apps/api/src/services/admin-clientes.service.ts create mode 100644 apps/api/src/services/admin-dashboard.service.ts create mode 100644 apps/api/src/services/alertas-auto.service.ts create mode 100644 apps/api/src/services/alertas-manuales.service.ts create mode 100644 apps/api/src/services/alertas.service.ts create mode 100644 apps/api/src/services/auth.service.ts create mode 100644 apps/api/src/services/bancos.service.ts create mode 100644 apps/api/src/services/calendario-fiscal.service.ts create mode 100644 apps/api/src/services/cartera.service.ts create mode 100644 apps/api/src/services/cfdi.service.ts create mode 100644 apps/api/src/services/conciliacion.service.ts create mode 100644 apps/api/src/services/connector.service.ts create mode 100644 apps/api/src/services/constancia.service.ts create mode 100644 apps/api/src/services/contribuyente-facturapi.service.ts create mode 100644 apps/api/src/services/contribuyente-fiel.service.ts create mode 100644 apps/api/src/services/contribuyente.service.ts create mode 100644 apps/api/src/services/dashboard.service.ts create mode 100644 apps/api/src/services/declaraciones.service.ts create mode 100644 apps/api/src/services/despacho-stats.service.ts create mode 100644 apps/api/src/services/despacho.service.ts create mode 100644 apps/api/src/services/documentos-extras.service.ts create mode 100644 apps/api/src/services/email/email.service.ts create mode 100644 apps/api/src/services/email/templates/alertas-nuevas.ts create mode 100644 apps/api/src/services/email/templates/base.ts create mode 100644 apps/api/src/services/email/templates/despacho-welcome.ts create mode 100644 apps/api/src/services/email/templates/documento-subido.ts create mode 100644 apps/api/src/services/email/templates/fiel-notification.ts create mode 100644 apps/api/src/services/email/templates/new-client-admin.ts create mode 100644 apps/api/src/services/email/templates/papeleria.ts create mode 100644 apps/api/src/services/email/templates/password-reset.ts create mode 100644 apps/api/src/services/email/templates/payment-confirmed.ts create mode 100644 apps/api/src/services/email/templates/payment-failed.ts create mode 100644 apps/api/src/services/email/templates/recordatorio-proximo.ts create mode 100644 apps/api/src/services/email/templates/subscription-cancelled.ts create mode 100644 apps/api/src/services/email/templates/subscription-expiring.ts create mode 100644 apps/api/src/services/email/templates/tarea-completada.ts create mode 100644 apps/api/src/services/email/templates/trial-reminder.ts create mode 100644 apps/api/src/services/email/templates/weekly-update.ts create mode 100644 apps/api/src/services/email/templates/welcome.ts create mode 100644 apps/api/src/services/export.service.ts create mode 100644 apps/api/src/services/facturapi.service.ts create mode 100644 apps/api/src/services/fiel.service.ts create mode 100644 apps/api/src/services/impuestos.service.ts create mode 100644 apps/api/src/services/metabase.service.ts create mode 100644 apps/api/src/services/metricas-compute.service.ts create mode 100644 apps/api/src/services/metricas.service.ts create mode 100644 apps/api/src/services/notification-preferences.service.ts create mode 100644 apps/api/src/services/notifications.service.ts create mode 100644 apps/api/src/services/notify-upload.service.ts create mode 100644 apps/api/src/services/obligaciones.service.ts create mode 100644 apps/api/src/services/opinion-cumplimiento.service.ts create mode 100644 apps/api/src/services/papeleria.service.ts create mode 100644 apps/api/src/services/payment/addon.service.ts create mode 100644 apps/api/src/services/payment/invoicing.service.ts create mode 100644 apps/api/src/services/payment/mercadopago.service.ts create mode 100644 apps/api/src/services/payment/subscription.service.ts create mode 100644 apps/api/src/services/plan-catalogo.service.ts create mode 100644 apps/api/src/services/recordatorios.service.ts create mode 100644 apps/api/src/services/regimen.service.ts create mode 100644 apps/api/src/services/reportes.service.ts create mode 100644 apps/api/src/services/sat/sat-auth.service.ts create mode 100644 apps/api/src/services/sat/sat-client.service.ts create mode 100644 apps/api/src/services/sat/sat-crypto.service.ts create mode 100644 apps/api/src/services/sat/sat-csf-login.ts create mode 100644 apps/api/src/services/sat/sat-csf-parser.ts create mode 100644 apps/api/src/services/sat/sat-csf-scraper.ts create mode 100644 apps/api/src/services/sat/sat-download.service.ts create mode 100644 apps/api/src/services/sat/sat-opinion-login.ts create mode 100644 apps/api/src/services/sat/sat-opinion-parser.ts create mode 100644 apps/api/src/services/sat/sat-opinion-scraper.ts create mode 100644 apps/api/src/services/sat/sat-parser.service.ts create mode 100644 apps/api/src/services/sat/sat.service.ts create mode 100644 apps/api/src/services/sat/sweep-stale-jobs.service.ts create mode 100644 apps/api/src/services/tareas.service.ts create mode 100644 apps/api/src/services/tenants.service.ts create mode 100644 apps/api/src/services/usuarios.service.ts create mode 100644 apps/api/src/utils/audit.ts create mode 100644 apps/api/src/utils/contribuyente-context.ts create mode 100644 apps/api/src/utils/entidades-visibles.ts create mode 100644 apps/api/src/utils/errors.ts create mode 100644 apps/api/src/utils/global-admin.ts create mode 100644 apps/api/src/utils/memberships.ts create mode 100644 apps/api/src/utils/metricas-cache.ts create mode 100644 apps/api/src/utils/platform-admin.ts create mode 100644 apps/api/src/utils/saldo.ts create mode 100644 apps/api/tsconfig.json create mode 100644 apps/web/.env.example create mode 100644 apps/web/app/(auth)/forgot-password/page.tsx create mode 100644 apps/web/app/(auth)/layout.tsx create mode 100644 apps/web/app/(auth)/login/page.tsx create mode 100644 apps/web/app/(auth)/register-despacho/page.tsx create mode 100644 apps/web/app/(auth)/register/page.tsx create mode 100644 apps/web/app/(auth)/reset-password/page.tsx create mode 100644 apps/web/app/(dashboard)/admin/audit-log/page.tsx create mode 100644 apps/web/app/(dashboard)/admin/staff/page.tsx create mode 100644 apps/web/app/(dashboard)/admin/usuarios/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/cancelaciones-periodo-anterior/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/cancelaciones/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/concentracion-clientes/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/concentracion-proveedores/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/discrepancia-regimen/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/efectivo/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/lista-negra-clientes/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/lista-negra-proveedores/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/page.tsx create mode 100644 apps/web/app/(dashboard)/alertas/tipo-relacion-sospechosa/page.tsx create mode 100644 apps/web/app/(dashboard)/calendario/page.tsx create mode 100644 apps/web/app/(dashboard)/carteras/page.tsx create mode 100644 apps/web/app/(dashboard)/cfdi/page.tsx create mode 100644 apps/web/app/(dashboard)/clientes/page.tsx create mode 100644 apps/web/app/(dashboard)/conciliacion/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/addons/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/csd/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/facturacion/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/notificaciones/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/obligaciones/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/precios-suscripcion/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/precios-timbres/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/sat/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/seguridad/page.tsx create mode 100644 apps/web/app/(dashboard)/configuracion/suscripcion/page.tsx create mode 100644 apps/web/app/(dashboard)/contribuyentes/addons-dialog.tsx create mode 100644 apps/web/app/(dashboard)/contribuyentes/page.tsx create mode 100644 apps/web/app/(dashboard)/dashboard/page.tsx create mode 100644 apps/web/app/(dashboard)/despachos/contribuyentes/page.tsx create mode 100644 apps/web/app/(dashboard)/despachos/equipo/page.tsx create mode 100644 apps/web/app/(dashboard)/despachos/mis-asignados/page.tsx create mode 100644 apps/web/app/(dashboard)/despachos/page.tsx create mode 100644 apps/web/app/(dashboard)/documentos/page.tsx create mode 100644 apps/web/app/(dashboard)/drill-down/page.tsx create mode 100644 apps/web/app/(dashboard)/facturacion/page.tsx create mode 100644 apps/web/app/(dashboard)/facturacion/timbres/page.tsx create mode 100644 apps/web/app/(dashboard)/impuestos/page.tsx create mode 100644 apps/web/app/(dashboard)/layout.tsx create mode 100644 apps/web/app/(dashboard)/mis-empresas/page.tsx create mode 100644 apps/web/app/(dashboard)/onboarding/page.tsx create mode 100644 apps/web/app/(dashboard)/pendientes/page.tsx create mode 100644 apps/web/app/(dashboard)/reportes/page.tsx create mode 100644 apps/web/app/(dashboard)/usuarios/page.tsx create mode 100644 apps/web/app/globals.css create mode 100644 apps/web/app/layout.tsx create mode 100644 apps/web/app/page.tsx create mode 100644 apps/web/app/terminos/page.tsx create mode 100644 apps/web/components/cfdi/cfdi-invoice.tsx create mode 100644 apps/web/components/cfdi/cfdi-viewer-modal.tsx create mode 100644 apps/web/components/charts/bar-chart.tsx create mode 100644 apps/web/components/charts/index.ts create mode 100644 apps/web/components/contribuyente-selector.tsx create mode 100644 apps/web/components/despachos/despacho-subnav.tsx create mode 100644 apps/web/components/documentos/papeleria-tab.tsx create mode 100644 apps/web/components/fiscal-disclaimer.tsx create mode 100644 apps/web/components/impuestos/activos-fijos-tab.tsx create mode 100644 apps/web/components/layouts/dashboard-shell.tsx create mode 100644 apps/web/components/layouts/header.tsx create mode 100644 apps/web/components/layouts/sidebar-compact.tsx create mode 100644 apps/web/components/layouts/sidebar-floating.tsx create mode 100644 apps/web/components/layouts/sidebar.tsx create mode 100644 apps/web/components/layouts/topnav.tsx create mode 100644 apps/web/components/membership-switcher.tsx create mode 100644 apps/web/components/obligaciones/tareas-tab.tsx create mode 100644 apps/web/components/onboarding/OnboardingScreen.tsx create mode 100644 apps/web/components/periodo-selector.tsx create mode 100644 apps/web/components/providers/query-provider.tsx create mode 100644 apps/web/components/providers/theme-provider.tsx create mode 100644 apps/web/components/sat/FielUploadModal.tsx create mode 100644 apps/web/components/sat/SyncHistory.tsx create mode 100644 apps/web/components/sat/SyncStatus.tsx create mode 100644 apps/web/components/subscription-banner.tsx create mode 100644 apps/web/components/tenant-selector.tsx create mode 100644 apps/web/content/terminos.ts create mode 100644 apps/web/lib/api/addons.ts create mode 100644 apps/web/lib/api/admin-clientes.ts create mode 100644 apps/web/lib/api/alertas.ts create mode 100644 apps/web/lib/api/audit-log.ts create mode 100644 apps/web/lib/api/auth.ts create mode 100644 apps/web/lib/api/bancos.ts create mode 100644 apps/web/lib/api/calendario.ts create mode 100644 apps/web/lib/api/carteras.ts create mode 100644 apps/web/lib/api/catalogos.ts create mode 100644 apps/web/lib/api/cfdi.ts create mode 100644 apps/web/lib/api/client.ts create mode 100644 apps/web/lib/api/conciliacion.ts create mode 100644 apps/web/lib/api/constancias.ts create mode 100644 apps/web/lib/api/contribuyentes.ts create mode 100644 apps/web/lib/api/dashboard.ts create mode 100644 apps/web/lib/api/declaraciones.ts create mode 100644 apps/web/lib/api/documentos.ts create mode 100644 apps/web/lib/api/facturacion.ts create mode 100644 apps/web/lib/api/fiel.ts create mode 100644 apps/web/lib/api/impuestos.ts create mode 100644 apps/web/lib/api/platform-staff.ts create mode 100644 apps/web/lib/api/reportes.ts create mode 100644 apps/web/lib/api/sat.ts create mode 100644 apps/web/lib/api/subscription.ts create mode 100644 apps/web/lib/api/tenants.ts create mode 100644 apps/web/lib/api/usuarios.ts create mode 100644 apps/web/lib/export-excel.ts create mode 100644 apps/web/lib/hooks/use-addons.ts create mode 100644 apps/web/lib/hooks/use-alertas.ts create mode 100644 apps/web/lib/hooks/use-audit-log.ts create mode 100644 apps/web/lib/hooks/use-bancos.ts create mode 100644 apps/web/lib/hooks/use-calendario.ts create mode 100644 apps/web/lib/hooks/use-carteras.ts create mode 100644 apps/web/lib/hooks/use-cfdi.ts create mode 100644 apps/web/lib/hooks/use-conciliacion.ts create mode 100644 apps/web/lib/hooks/use-constancias.ts create mode 100644 apps/web/lib/hooks/use-contribuyentes.ts create mode 100644 apps/web/lib/hooks/use-dashboard.ts create mode 100644 apps/web/lib/hooks/use-declaraciones.ts create mode 100644 apps/web/lib/hooks/use-documentos.ts create mode 100644 apps/web/lib/hooks/use-facturacion.ts create mode 100644 apps/web/lib/hooks/use-impuestos.ts create mode 100644 apps/web/lib/hooks/use-nav-gate.ts create mode 100644 apps/web/lib/hooks/use-platform-staff.ts create mode 100644 apps/web/lib/hooks/use-reportes.ts create mode 100644 apps/web/lib/hooks/use-subscription.ts create mode 100644 apps/web/lib/hooks/use-tenants.ts create mode 100644 apps/web/lib/hooks/use-usuarios.ts create mode 100644 apps/web/lib/onboarding.ts create mode 100644 apps/web/lib/utils.ts create mode 100644 apps/web/next-env.d.ts create mode 100644 apps/web/next.config.js create mode 100644 apps/web/package.json create mode 100644 apps/web/postcss.config.js create mode 100644 apps/web/public/legal/terminos-y-condiciones.pdf create mode 100644 apps/web/public/logo.jpg create mode 100644 apps/web/stores/auth-store.ts create mode 100644 apps/web/stores/contribuyente-store.ts create mode 100644 apps/web/stores/periodo-store.ts create mode 100644 apps/web/stores/tenant-view-store.ts create mode 100644 apps/web/stores/theme-store.ts create mode 100644 apps/web/tailwind.config.ts create mode 100644 apps/web/themes/corporate.ts create mode 100644 apps/web/themes/dark.ts create mode 100644 apps/web/themes/index.ts create mode 100644 apps/web/themes/light.ts create mode 100644 apps/web/themes/vibrant.ts create mode 100644 apps/web/tsconfig.json create mode 100644 deploy/nginx/horux360.conf create mode 100644 deploy/systemd/horux-api.service create mode 100644 deploy/systemd/horux-web.service create mode 100644 docker-compose.yml create mode 100644 docs/Horux_despachos-vs-Horux360.md create mode 100644 docs/SAT-SYNC-IMPLEMENTATION.md create mode 100644 docs/architecture/api-reference.md create mode 100644 docs/architecture/database-schema.prisma create mode 100644 docs/architecture/deployment.md create mode 100644 docs/legal/Terminos y condiciones.pdf create mode 100644 docs/plans/2026-01-22-fase1-implementation.md create mode 100644 docs/plans/2026-01-22-fase2-implementation.md create mode 100644 docs/plans/2026-01-22-fase3-implementation.md create mode 100644 docs/plans/2026-01-22-horux360-saas-design.md create mode 100644 docs/plans/2026-01-25-sat-sync-design.md create mode 100644 docs/plans/2026-01-25-sat-sync-implementation.md create mode 100644 docs/plans/2026-02-17-cfdi-viewer-design.md create mode 100644 docs/plans/2026-02-17-cfdi-viewer-implementation.md create mode 100644 docs/plans/2026-04-13-auto-invoicing-mp-payments.md create mode 100644 docs/plans/2026-04-13-cfdi-export-filters.md create mode 100644 docs/plans/2026-04-13-rol-admin-to-owner-rename.md create mode 100644 docs/plans/2026-04-13-sat-incremental-enterprise.md create mode 100644 docs/plans/2026-04-13-subscriptions-self-serve.md create mode 100644 docs/plans/2026-04-13-typescript-debt-cleanup.md create mode 100644 docs/plans/2026-04-14-audit-log.md create mode 100644 docs/plans/2026-04-14-documentos-csf-declaraciones.md create mode 100644 docs/plans/2026-04-14-jwt-revocation.md create mode 100644 docs/plans/2026-04-14-owner-multi-rfc-subscriptions.md create mode 100644 docs/plans/2026-04-14-password-reset.md create mode 100644 docs/plans/2026-04-14-platform-admin-roles.md create mode 100644 docs/plans/2026-04-14-postgres-pitr.md create mode 100644 docs/plans/2026-04-14-rate-limiting-expansion.md create mode 100644 docs/plans/2026-04-14-reactivate-subscription.md create mode 100644 docs/plans/2026-04-14-trial-abuse-prevention.md create mode 100644 docs/plans/2026-04-18-session-fixes-and-features.md create mode 100644 docs/plans/2026-04-19-pending-features.md create mode 100644 docs/plans/2026-04-19-session-2-fixes-and-features.md create mode 100644 docs/plans/2026-04-21-facturacion-fixes-session.md create mode 100644 docs/plans/2026-04-21-session-2-mp-setup-and-bugfixes.md create mode 100644 docs/plans/2026-04-22-pendientes-y-addons.md create mode 100644 docs/plans/2026-04-23-features-fixes-y-derivados.md create mode 100644 docs/plans/2026-04-24-session-fixes-and-features.md create mode 100644 docs/plans/2026-04-25-despacho-tareas-papeleria.md create mode 100644 docs/plans/2026-04-26-admin-global-setup.md create mode 100644 docs/plans/2026-04-26-i07-ppd-compensacion.md create mode 100644 docs/plans/2026-04-26-iva-refactor.md create mode 100644 docs/plans/2026-04-26-notifications-email.md create mode 100644 docs/plans/2026-04-26-rebrand-planes-despacho.md create mode 100644 docs/plans/2026-04-26-session.md create mode 100644 docs/plans/2026-04-26-sprints-1-2-3.md create mode 100644 docs/plans/2026-04-27-i07-ppd-pue-only-y-cfdi-tipo-filter.md create mode 100644 docs/plans/2026-04-27-session.md create mode 100644 docs/plans/2026-04-30-session.md create mode 100644 docs/plans/2026-05-01-session.md create mode 100644 docs/plans/2026-05-02-session.md create mode 100644 docs/security/2026-03-18-security-audit-remediation.md create mode 100644 docs/superpowers/INDEX.md create mode 100644 docs/superpowers/plans/2026-03-15-saas-transformation.md create mode 100644 docs/superpowers/plans/2026-04-12-conciliacion-implementation.md create mode 100644 docs/superpowers/plans/2026-04-13-opinion-cumplimiento.md create mode 100644 docs/superpowers/plans/2026-04-13-tenant-migrations.md create mode 100644 docs/superpowers/plans/2026-04-16-refactor-monorepo-packages.md create mode 100644 docs/superpowers/plans/2026-04-17-plan2a-schema-auth.md create mode 100644 docs/superpowers/plans/2026-04-17-plan2b-contribuyentes-fiel-cfdi.md create mode 100644 docs/superpowers/plans/2026-04-17-plan2b2-fiel-facturapi-per-contribuyente.md create mode 100644 docs/superpowers/plans/2026-04-17-plan2c-frontend-despachos.md create mode 100644 docs/superpowers/plans/2026-04-17-plan3-roles-carteras.md create mode 100644 docs/superpowers/plans/2026-04-27-filtros-activos-ncs-impuestos-fase1.md create mode 100644 docs/superpowers/plans/2026-04-27-isr-base-gravable-acumulada.md create mode 100644 docs/superpowers/specs/2026-03-15-saas-transformation-design.md create mode 100644 docs/superpowers/specs/2026-04-12-conciliacion-design.md create mode 100644 docs/superpowers/specs/2026-04-12-sat-sync-chunking.md create mode 100644 docs/superpowers/specs/2026-04-13-opinion-cumplimiento-design.md create mode 100644 docs/superpowers/specs/2026-04-13-tenant-migrations-design.md create mode 100644 docs/superpowers/specs/2026-04-16-horux-despachos-design.md create mode 100644 docs/superpowers/specs/2026-04-27-custom-plan-design.md create mode 100644 docs/superpowers/specs/2026-04-27-drill-down-sort-by-name-design.md create mode 100644 docs/superpowers/specs/2026-04-27-filtros-activos-ncs-impuestos-fase1-design.md create mode 100644 docs/superpowers/specs/2026-04-27-isr-base-gravable-acumulada-design.md create mode 100644 docs/superpowers/specs/2026-04-27-trial-rfc-limit-design.md create mode 100644 ecosystem.config.js create mode 100644 errores_visuales_frontend/Base gravable 605.png create mode 100644 errores_visuales_frontend/Base gravable 621.png create mode 100644 errores_visuales_frontend/Planes.registro 2.png create mode 100644 errores_visuales_frontend/Planes.registro 3.png create mode 100644 errores_visuales_frontend/Planes.registro.png create mode 100644 errores_visuales_frontend/Recuperar contraseña.png create mode 100644 errores_visuales_frontend/Reportes.png create mode 100644 lista_negra/Listado_completo_69-B.csv create mode 100644 package.json create mode 100644 packages/core/package.json create mode 100644 packages/core/src/auth/index.ts create mode 100644 packages/core/src/auth/password.ts create mode 100644 packages/core/src/auth/token.ts create mode 100644 packages/core/src/crypto/aes-gcm.ts create mode 100644 packages/core/src/crypto/index.ts create mode 100644 packages/core/src/email/index.ts create mode 100644 packages/core/src/email/transport.ts create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/tsconfig.json create mode 100644 packages/shared-ui/package.json create mode 100644 packages/shared-ui/src/charts/index.ts create mode 100644 packages/shared-ui/src/charts/kpi-card.tsx create mode 100644 packages/shared-ui/src/form/index.ts create mode 100644 packages/shared-ui/src/form/period-selector.tsx create mode 100644 packages/shared-ui/src/form/regimen-selector.tsx create mode 100644 packages/shared-ui/src/hooks/index.ts create mode 100644 packages/shared-ui/src/hooks/use-debounce.ts create mode 100644 packages/shared-ui/src/hooks/use-table-sort.ts create mode 100644 packages/shared-ui/src/index.ts create mode 100644 packages/shared-ui/src/lib/cn.ts create mode 100644 packages/shared-ui/src/lib/index.ts create mode 100644 packages/shared-ui/src/primitives/button.tsx create mode 100644 packages/shared-ui/src/primitives/card.tsx create mode 100644 packages/shared-ui/src/primitives/dialog.tsx create mode 100644 packages/shared-ui/src/primitives/index.ts create mode 100644 packages/shared-ui/src/primitives/input.tsx create mode 100644 packages/shared-ui/src/primitives/label.tsx create mode 100644 packages/shared-ui/src/primitives/popover.tsx create mode 100644 packages/shared-ui/src/primitives/select.tsx create mode 100644 packages/shared-ui/src/primitives/sortable-header.tsx create mode 100644 packages/shared-ui/src/primitives/tabs.tsx create mode 100644 packages/shared-ui/tailwind-preset.js create mode 100644 packages/shared-ui/tsconfig.json create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/constants/despacho-plans.ts create mode 100644 packages/shared/src/constants/roles.ts create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/src/types/alertas.ts create mode 100644 packages/shared/src/types/auth.ts create mode 100644 packages/shared/src/types/calendario.ts create mode 100644 packages/shared/src/types/cfdi.ts create mode 100644 packages/shared/src/types/dashboard.ts create mode 100644 packages/shared/src/types/despacho.ts create mode 100644 packages/shared/src/types/documentos.ts create mode 100644 packages/shared/src/types/impuestos.ts create mode 100644 packages/shared/src/types/reportes.ts create mode 100644 packages/shared/src/types/sat.ts create mode 100644 packages/shared/src/types/subscription.ts create mode 100644 packages/shared/src/types/tenant.ts create mode 100644 packages/shared/src/types/user.ts create mode 100644 packages/shared/tsconfig.json create mode 100644 packages/vertical-contable/package.json create mode 100644 packages/vertical-contable/src/index.ts create mode 100644 packages/vertical-contable/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/backup.sh create mode 100644 scripts/tune-postgres.sh create mode 100644 scripts/update-cfdi-xml.js create mode 100644 turbo.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d48700e --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Build +.next/ +out/ +dist/ +build/ + +# Environment +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Testing +coverage/ +.nyc_output/ + +# Prisma +prisma/*.db +prisma/*.db-journal + +# Misc +*.log +*.tsbuildinfo +.turbo/ + +# Runtime data (XMLs descargados del SAT, nunca subir — datos fiscales reales) +apps/api/data/ + +# Email template previews (regenerados con `pnpm email:preview`) +email-previews/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4382a4c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,523 @@ +# Horux360 - Contexto para Claude + +## Qué es esto + +Plataforma SaaS de análisis financiero y gestión fiscal para empresas mexicanas. Maneja CFDIs (facturas electrónicas del SAT), cálculos de IVA/ISR, reportes financieros, alertas de cumplimiento fiscal, conciliación bancaria, sincronización directa con el SAT y emisión de facturas vía Facturapi. + +**Producción:** https://horuxfin.com +**Autor:** Carlos e Ivan (Horux 360) (RFC: HTS240708LJA) + +--- + +## Arquitectura en 30 segundos + +``` +Monorepo (pnpm + Turborepo) +├── apps/api → Express + TypeScript (tsx, puerto 4000) +├── apps/web → Next.js 14 + App Router (puerto 3000) +└── packages/shared → Tipos, constantes e interfaces compartidas +``` + +**Multi-tenant database-per-tenant:** +- `horux360` → BD central (Prisma): tenants, users, roles, subscriptions, catálogos fiscales, catálogos SAT, timbres Facturapi +- `horux_` → BD por tenant (pg Pool + raw SQL): cfdis, cfdi_conceptos, rfcs, bancos, conciliaciones, alertas, recordatorios, contribuyentes, carteras, obligaciones, tareas, papelería + +**Dos capas de acceso a datos** (esto es intencional): +- **Prisma** para la BD central (ORM, migraciones, tipos generados) +- **pg Pool directo** para BDs de tenant (queries SQL complejos de cálculos fiscales) + +--- + +## Archivos clave por área + +### Configuración +| Archivo | Qué hace | +|---------|----------| +| `apps/api/src/config/env.ts` | Variables de entorno validadas con Zod (incluye FACTURAPI_USER_KEY) | +| `apps/api/src/config/database.ts` | Prisma client + `TenantConnectionManager` (pools, provisioning, lazy migration) | +| `apps/api/src/config/tenant-migrations.ts` | `migrate()`, `migrateAll()`, `getMigrationFiles()` — sistema de migraciones SQL para BDs tenant | +| `apps/api/src/migrations/tenant/*.sql` | Archivos SQL numerados (`001_initial_schema.sql`, etc.) | +| `apps/api/.env` | Credenciales locales (DB, JWT, SMTP, MercadoPago, FIEL, Facturapi, Metabase) | +| `apps/api/prisma/schema.prisma` | Schema de la BD central | + +### Autenticación y seguridad +| Archivo | Qué hace | +|---------|----------| +| `src/middlewares/auth.middleware.ts` | JWT verify + `authorize(...roles)` | +| `src/middlewares/tenant.middleware.ts` | Resuelve pool de BD del tenant, cache 5min, `X-View-Tenant` para admin global | +| `src/middlewares/plan-limits.middleware.ts` | Verifica suscripción, limita CFDIs, read-only si inactiva | +| `src/middlewares/feature-gate.middleware.ts` | `requireFeature('reportes')` por plan | +| `src/utils/global-admin.ts` | Admin global = tenant con RFC `HTS240708LJA` | +| `packages/shared/src/constants/roles.ts` | `GLOBAL_ADMIN_RFC` + `isGlobalAdminRfc()` compartido frontend/backend | + +### Roles (tabla `roles` en BD central) +| id | nombre | Label UI | Acceso | +|----|--------|----------|--------| +| 1 | `owner` | Dueño | Todo + gestión usuarios + configuración + reportes | +| 7 | `cfo` | CFO | Todo (mismo nivel que owner) | +| 2 | `contador` | Contador | Dashboard, CFDI, Impuestos, Calendario, Alertas, Conciliación, Facturación (puede completar alertas y crear recordatorios) | +| 8 | `auxiliar` | Auxiliar | Mismos permisos que contador | +| 3 | `visor` | Visor | CFDI, Impuestos, Calendario, Alertas, Conciliación (solo lectura) | + +**"Admin global" (platform staff) — tabla `user_platform_roles`:** staff interno de Horux 360 con acceso transversal. 5 roles en enum `PlatformRole`: +- `platform_admin` — Todo (gestión staff, precios, clientes, facturas) +- `platform_ti` — Mismos permisos que admin (equipo TI, trazabilidad distinta en audit) +- `platform_support` — Ver tenants, tickets; NO facturación/precios +- `platform_sales` — Crear/editar clientes; NO precios +- `platform_finance` — Pagos, facturas manuales, editar precios + +`platform_admin` y `platform_ti` son **supersets** (implican todos los demás). Se checa vía helper: `hasPlatformRole(userId, role)`, `canManageTenants()`, `canEditPrices()`, `canEmitInvoicesManual()`, `isPlatformStaff()` en `apps/api/src/utils/platform-admin.ts`. JWT incluye `platformRoles[]` al login. + +**`isGlobalAdmin()` compat:** `apps/api/src/utils/global-admin.ts` es shim que re-exporta de `platform-admin.ts` — resuelve "admin global" vía tabla (busca rol superset), fallback a RFC `HTS240708LJA` si tabla vacía. El concepto UX "admin global" se preserva; la implementación migró de hardcode a tabla. + +**`isGlobalAdminRfc(rfc, role, platformRoles?)` en shared:** tercer parámetro opcional — prioriza `platformRoles` si existe, fallback al RFC check. + +**UI `/admin/staff`:** gestión de roles (solo platform_admin/platform_ti). Protección "último superset": no te puedes quitar si serías el único con acceso transversal. Ver `docs/plans/2026-04-14-platform-admin-roles.md` — sección "Implementación ejecutada". + +### Planes (5 planes) +| Plan | CFDIs | Usuarios | Features | +|------|-------|----------|----------| +| starter | 0 | 1 | dashboard, cfdi_basic, iva_isr | +| business | 50 | 3 | + reportes, alertas, calendario, conciliacion, forecasting, xml_sat, documentos | +| business_ia | 50 | 3 | Mismas que business + Lolita (feature `ia_lolita`, agente IA fiscal) | +| custom | 50 | 3 | Mismas que business_ia, precio variable por tenant | +| enterprise | 100 | ilimitado | + api | + +Cada empresa requiere su propia suscripción. `multi_empresa` no existe como feature. + +### Suscripciones (self-serve con MercadoPago) + +**Precios:** tabla `plan_prices` en BD central — 8 filas (4 planes × monthly/annual). Custom no se guarda aquí (el admin fija monto por tenant). Editables desde `/configuracion/suscripcion` (admin global) o SQL directo. Los cambios aplican solo a suscripciones nuevas; vigentes conservan su amount. + +**Estados de `Subscription.status`:** `trial`, `trial_converted`, `trial_expired`, `pending`, `authorized`, `paused`, `cancelled`. + +**Flujos self-serve (endpoints `/api/subscriptions/me/*`):** +- `trial` — 30 días gratis sin tarjeta. Una sola vez **por RFC** (padrón persistente `trial_usages` — sobrevive borrado/recreación del tenant, bloquea abuso). +- `subscribe` — crea preapproval MP con `auto_recurring` mensual (`months/1`) o anual (`months/12`). +- `change` — downgrades y cambios de frecuencia van a `pendingPlan`/`pendingEffectiveAt = currentPeriodEnd`. +- `upgrade` — plan más caro con misma frecuencia cobra prorateo inmediato vía MP Preference + `updatePreapprovalAmount` al recibir webhook. `external_reference = 'proration:${tenantId}:${subscriptionId}'` es el marcador que el webhook usa. +- `cancel` — `status=cancelled` + `cancelPreapproval` en MP. Middleware respeta `currentPeriodEnd`. +- `reactivate` — revive suscripción cancelada dentro del período pagado. Crea preapproval nuevo con `start_date=currentPeriodEnd` (sin doble cobro). Limpia pending/upgrade residuales. + +**Cron `applyPendingChanges` + `expireTrials`:** 2:30 AM daily (`30 2 * * *`) — aplica cambios programados y transiciona trials vencidos. + +**Auth pattern:** endpoints `/subscriptions/:tenantId`, `/:tenantId/payments`, `/:tenantId/generate-link` usan `requireOwnTenantOrGlobalAdmin` (el dueño puede consultar su propia sub, admin global puede ver cualquiera). `mark-paid`, `listar todas`, `editar precios` solo admin global. + +**Webhook `external_reference` routing:** +- `proration:*` → `applyApprovedUpgrade` (upgrade payment confirmado) +- `${tenantId}` UUID → flujo recurrente + +**Plan Custom:** sólo lo activa el admin global al provisionar tenant. El self-serve self-serve lo rechaza con error explícito. + +**Auto-facturación de pagos (`invoicing.service.ts`):** cada webhook `payment.approved` dispara `emitInvoiceIfApplicable(payment.id)`. Emite CFDI vía Facturapi (org de Horux 360, RESICO PM, clave prod/serv `81112502`). **El primer pago aprobado de cada tenant se skip-ea** (lo factura manual el admin para capturar datos fiscales); los subsecuentes van auto. **Receptor según preferencia del tenant** (`Tenant.factPreferencia` — `mis_datos` por default vs `publico_general`): si tiene CSF cargada + `factPreferencia='mis_datos'` se factura con datos reales del cliente usando `factUsoCfdi` (default `G03`) y `factRegimenPreferido` (o primer régimen activo si no se especifica); si falta cualquier dato fiscal cae automáticamente a Público en General + `S01`. Configurable desde `/configuracion/facturacion` (UI limita uso CFDI a G03/S01). Idempotente por `Payment.facturapiInvoiceId`. Fail-soft (error en Facturapi → log + invoice null + webhook sigue retornando 200). Ver `docs/plans/2026-04-13-auto-invoicing-mp-payments.md` y `docs/plans/2026-05-02-session.md` sección 22. + +Ver `docs/plans/2026-04-13-subscriptions-self-serve.md` para diseño completo y flujos. + +**Schema tenant — single source of truth:** las migraciones SQL en `apps/api/src/migrations/tenant/*.sql` son el único lugar donde se declara el schema de las BDs tenant. El seed (`prisma/seed.ts`) ya NO hardcodea CREATE TABLE — llama a `migrate()` del runner. Añadir una migración = crear `NNN_descripcion.sql`. Se aplica eager via `pnpm db:migrate-tenants` y lazy en `getPool()`. + +**Audit log (`utils/audit.ts`):** helper `auditLog()`/`auditFromReq()` fire-and-forget que escribe a tabla `audit_log` (BD central). Instrumentado en 12+ eventos críticos (login, subscribe, cancel, plan change, price edit, invoice auto, password reset, etc.). UI admin global en `/admin/audit-log` con filtros + expand JSON metadata. NUNCA debe romper la acción principal — cualquier fallo al escribir se logea en consola pero no re-lanza. Ver `docs/plans/2026-04-14-audit-log.md`. + +**Recuperación de contraseña:** `/login` tiene link "¿Olvidaste tu contraseña?" → `/forgot-password` (solicita token por email) → `/reset-password?token=xxx` (nueva password). Backend: tabla `password_reset_tokens` (32 bytes hex, 1h expiry, single-use), endpoints `POST /auth/password-reset/{request,confirm}` con rate limits 3/h y 10/h. Anti-enumeration (respuesta genérica). Al confirmar reset se borran todos los refresh tokens del user. SMTP logea a consola en dev — copia el link del log del API para testing local. Ver `docs/plans/2026-04-14-password-reset.md`. + +**Onboarding auto-dismiss:** `/onboarding` muestra los pasos de configuración inicial (cuenta → contribuyente → FIEL → CSD → equipo opc. → plan opc.) para owner/cfo/contador. Deja de mostrarse cuando se cumple cualquiera de las dos condiciones (lo que pase primero): (1) `User.loginCount > 4` — incrementado solo en `auth.service.login()`, NO en refresh, para que el threshold sea sesiones reales y no horas; (2) `User.onboardingDismissedAt != null` — seteado por `POST /auth/onboarding/dismiss` (idempotente, preserva timestamp original) que la página `/onboarding` invoca automáticamente cuando todos los pasos requeridos están completos. Helper compartido en `apps/web/lib/onboarding.ts:shouldShowOnboarding(user)` con `ONBOARDING_LOGIN_THRESHOLD = 4` exportado. El `LoginResponse.user` incluye ambos campos para que el frontend decida sin round-trip extra. Roles `cliente/auxiliar/supervisor` y admin global nunca ven onboarding (van directo a `/dashboard` o `/clientes`). Ver `docs/plans/2026-05-02-session.md` sección 23. + +**Rate limiting por endpoint (`middlewares/rate-limit.middleware.ts`):** 4 tiers con key por `userId` (no IP, para no bloquear usuarios detrás de NAT compartido). Admin global (superset `platform_admin`/`platform_ti`) exento vía `skip`. Tiers: `veryStrictLimit` (2/día) en sync SAT manual y opinión cumplimiento; `strictLimit` (10/h) en emisión/cancelación factura, CFDI bulk, subs subscribe/change/upgrade, password-change; `normalLimit` (100/15min) a router level en dashboard/reportes/impuestos; `relaxedLimit` (500/15min) en catalogos/regimenes. `/auth/login` y `/auth/register` conservan sus propios limiters específicos. Headers `RateLimit-*` estándar. Key generator: usa `ipKeyGenerator(req.ip)` de `express-rate-limit` para el fallback anónimo — normaliza IPv6 correctamente (sin esto el lib emitía warning de potential bypass). Frontend (`lib/api/client.ts`) preserva el `message` del 429 para los try/catch existentes. Ver `docs/plans/2026-04-14-rate-limiting-expansion.md`. + +**Input validation con Zod (defense-in-depth):** todos los controllers críticos que reciben `req.body` validan el shape con Zod antes de pasar al service. Prisma ya protege de SQL injection, pero Zod evita que un payload malformado llegue hasta la capa de persistencia y produzca 500s o comportamiento raro. Cobertura: `auth.controller` (register/login/refresh/reset/change/logout-all/switch-tenant), `bancos.controller` (create/update), `alertas.controller` (create/update), `calendario.controller` (create/update recordatorio), `usuarios.controller` (invite/update/updateGlobal), `tenants.controller` (addMyTenant), `subscription.controller` (varios), `platform-staff.controller`. Patrón uniforme: schema arriba del archivo, `.parse(req.body)` en el handler, catch `z.ZodError` → `next(new AppError(400, error.errors[0].message))`. Los schemas tipo `UserInvite`/`UserUpdate` en `@horux/shared` se mantienen alineados con los Zod enums. + +**Helmet configurado explícitamente (`app.ts`):** `helmet({ contentSecurityPolicy: false, crossOriginResourcePolicy: 'cross-origin' })`. CSP se desactiva en el API porque (a) el API solo sirve JSON y binarios (PDF/XML) sin contenido HTML que requiera CSP, y (b) el default chocaba con el iframe del PDF que el frontend embebe en `/terminos`. Los security headers del frontend (Next) siguen siendo la capa de CSP activa: ver `apps/web/next.config.js` con `X-Frame-Options: SAMEORIGIN`, `Content-Security-Policy: frame-ancestors 'self'`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, HSTS. + +**Multi-tenant memberships (owner-multi-rfc, en progreso):** Nueva tabla `tenant_memberships (user_id, tenant_id, rol_id, is_owner, active, joined_at)` con `@@unique([userId, tenantId])`. Permite que un mismo user pertenezca a múltiples tenants con rol distinto en cada uno. Backfilled idempotente desde `User.tenantId+rolId` (1 membership por user existente). `User.tenantId` y `User.rolId` se **mantienen** durante la transición como "default tenant" para UX al login. Login y refresh responses incluyen `user.tenants: [{id, nombre, rfc, plan, role, isOwner}]` derivado de memberships activas. Endpoint `POST /auth/switch-tenant { tenantId, refreshToken }` valida membership, revoca el refresh actual, emite nuevo par apuntando al target con el rol de ese tenant (audit event `user.tenant_switched`). Helpers en `apps/api/src/utils/memberships.ts`: `getUserTenants()`, `verifyMembership()`. Frontend: `MembershipSwitcher` en el header (`apps/web/components/membership-switcher.tsx`) visible cuando `tenants.length > 1` y NO es admin global; click llama `switchTenant` + `queryClient.clear()` + reload. Coexiste con `TenantSelector` (admin global usa impersonación X-View-Tenant, modelo distinto). Página `/mis-empresas` (owner-only) lista solo tenants donde `isOwner=true` con su subscripción joined; modal "Agregar empresa" llama `POST /api/tenants/mine` (gateado por `isOwnerSomewhere(userId)` en backend → 403 si no eres owner en ningún lado). Sidebar item "Mis empresas" usa flag `requireOwnerSomewhere` (no `roles[]`) para que un híbrido contador+owner siga viéndolo desde cualquier contexto activo. Helpers: `addTenantToOwner()`, `getMyTenantsDetailed()` en `tenants.service.ts`; `isOwnerSomewhere()`, `getTenantOwnerEmail()` en `utils/memberships.ts`. Trial gate por owner: `startTrial()` acepta `ownerUserId` opcional — si se pasa, busca otro tenant donde el user es owner activo Y tiene `trialEndsAt` set → si encuentra, rechaza con mensaje citando el RFC previo. Combinado con `trial_usages` (gate por RFC), cubre todos los vectores de abuso del trial. **F6 cleanup completo (2026-04-14):** `User.tenantId` y `User.rolId` **eliminados** del schema. `User.lastTenantId String?` agregado para "remember last tenant" UX. `auth.login` resuelve activeMembership desde `tenant_memberships` (prefiere lastTenantId, fallback al primer membership por joinedAt). 5 callsites de subscription.service migrados a `getTenantOwnerEmail()`. usuarios.service todo via memberships. Fases 1-6 ✅ (refactor completo). Ver `docs/plans/2026-04-14-owner-multi-rfc-subscriptions.md`. + +**Sistema de emails (`apps/api/src/services/email/`):** transporte SMTP via Nodemailer (config en `.env`: `SMTP_HOST/PORT/USER/PASS`). Si SMTP no está configurado, los emails se logean a consola — útil en dev. 9 templates en `templates/`: `welcome`, `password-reset`, `payment-confirmed`, `payment-failed`, `subscription-cancelled`, `subscription-expiring`, `fiel-notification`, `new-client-admin`, `weekly-update`. Layout común en `base.ts` con identidad visual de horux360.com: header con gradiente azul→morado (`#2563EB → #7C3AED`), tipografía Inter (Google Fonts con fallback a system-ui), tokens de marca exportados como `BRAND_COLORS`. Helpers `primaryButton`, `infoBox`, `heading` para componentes consistentes. **Importante:** las familias de fuentes en `style="..."` deben usar comillas simples (`'Inter', ...`) — comillas dobles rompen el atributo HTML. Para previsualizar sin SMTP: `pnpm email:preview` genera HTMLs estáticos en `apps/api/email-previews/` (gitignored) con datos de ejemplo + index navegable. Ejecutar tras cualquier cambio de template para verificar visualmente. + +**Reporte semanal automático (`weekly-update.job.ts`):** cron Lunes 8:00 AM (`0 8 * * 1` America/Mexico_City) que itera todos los tenants activos y envía a cada owner activo el correo "Actualización semanal — {empresa}". Solo arranca en `NODE_ENV=production`. Contenido: KPIs del mes en curso (ingresos/egresos/utilidad/IVA balance/CFDIs emitidos+recibidos vía `getKpis()`), alertas activas (rojo/ámbar/azul por prioridad vía `generarAlertasAutomaticas()`), y breakdown de discrepancias de régimen por mes en los últimos 6 meses (`getDiscrepanciasPorMes(pool, tenantId, 6)`). Recipientes resueltos vía `tenant_memberships.where(isOwner=true, active=true)`. Por-tenant + por-recipient try/catch — un fallo individual no bloquea al resto. `sendWeeklyUpdateForTenant(tenantId)` exportado para disparo manual desde un endpoint admin futuro. + +**Revocación de JWT (tokenVersion):** `User.tokenVersion` (entero, default 0) se incluye en cada JWT. `authenticate` middleware compara `payload.tokenVersion` contra el actual en BD (cache 30s + broadcast PM2 via `process.send`). Incrementar el version invalida **todos** los access tokens del user en el siguiente request. Disparadores: (1) cambio de password autenticado (`POST /auth/password-change`), (2) "cerrar todas las sesiones" (`POST /auth/logout-all`), (3) confirmar password reset (`POST /auth/password-reset/confirm`). UI en `/configuracion/seguridad`. Backward compat: JWTs pre-deploy no incluyen el campo → se interpreta como 0 → matchea con default, no hay re-login forzado masivo. Ver `docs/plans/2026-04-14-jwt-revocation.md`. + +### Lógica fiscal (lo más complejo) +| Archivo | Qué hace | +|---------|----------| +| `src/services/dashboard.service.ts` | KPIs: ingresos/egresos/IVA/adquisición mercancías por régimen, IVA a favor acumulado. Filtro de conciliación. | +| `src/services/impuestos.service.ts` | IVA mensual, resumen IVA/ISR, coeficiente de utilidad | +| `src/services/cfdi.service.ts` | CRUD CFDIs, filtros, búsqueda, resúmenes, bulk insert | +| `src/services/conciliacion.service.ts` | Conciliación bancaria: vincular CFDIs con pagos | +| `src/services/bancos.service.ts` | CRUD bancos por tenant | +| `src/services/reportes.service.ts` | Estado de resultados, flujo efectivo, comparativo, CxP, CxC | +| `src/services/alertas-auto.service.ts` | Alertas automáticas: lista negra, concentración, discrepancias, cancelación retroactiva, TipoRelacion sospechoso, tareas próximas a vencer, RESICO PF cerca de límite anual | +| `src/services/calendario-fiscal.service.ts` | Genera calendario fiscal desde catálogo + días inhábiles | +| `src/services/regimen.service.ts` | Catálogo de regímenes, configuración activos/ignorados por tenant | +| `src/services/recordatorios.service.ts` | CRUD recordatorios custom por tenant (público/privado) | +| `src/services/tareas.service.ts` | Tareas operativas recurrentes por contribuyente con materialización lazy de periodos | +| `src/services/papeleria.service.ts` | Papelería de trabajo: archivos por contribuyente con flujo opcional de aprobación | +| `src/services/despacho-stats.service.ts` | Métricas del módulo Despacho: contribuyentes, mis-asignados, equipo (jerárquico) | +| `src/services/notification-preferences.service.ts` | Preferencias de email por contribuyente (JSONB en `contribuyentes`) | + +### Claves de concepto excluidas de cálculos fiscales +Los conceptos con las siguientes `clave_prod_serv` se excluyen de **todos** los cálculos de ingresos, egresos, IVA e ISR (dashboard + impuestos, base y conciliación): +- `84121603` — Seguros +- `93161608` — Servicios gubernamentales +- `85101501` — Servicios de salud +- `85121800` — Servicios médicos + +Implementado con subqueries correlacionados a `cfdi_conceptos` que restan los montos de conceptos excluidos por CFDI. Si un CFDI tiene otros conceptos válidos, solo se excluyen los que coinciden con estas claves. + +### Regla clave: régimen del tenant en queries fiscales +- `type = 'EMITIDO'` → el tenant es el emisor → usar `regimen_fiscal_emisor` +- `type = 'RECIBIDO'` → el tenant es el receptor → usar `regimen_fiscal_receptor` + +### Alerta RESICO PF — límite anual ($3.5M Art. 113-E LISR) +`alertaResicoPfLimiteIngresos` en `alertas-auto.service.ts` aplica solo cuando el contribuyente: (1) tiene RFC de 13 chars (PF), y (2) tiene `626` en su CSV `regimen_fiscal`. **El cálculo de ingresos NO filtra por régimen** — el SAT considera ingresos acumulados de TODOS los regímenes para el límite del Art. 113-E ($3.5M). Thresholds: $2.5M (media), $3M (alta), $3.5M (alta + "ya saliste del régimen"). Suma `I PUE + P − E PUE` del año en curso usando `total_mxn` (con IVA — proxy conservador para alerta preventiva). RESICO PM (RFC 12) no aplica — ese régimen tiene reglas distintas y se filtra por longitud de RFC. + +### Filtro de Conciliación (Dashboard + Impuestos) +Toggle global que cambia el comportamiento de todas las métricas: +- **Desactivado (default):** Usa `fecha_emision` y todos los CFDIs vigentes +- **Activado:** Solo CFDIs con `id_conciliacion IS NOT NULL`, usa `fecha_de_pago` de tabla `conciliaciones` +- Implementado con `getFechaRango(conciliacion)` que retorna el SQL fragment correcto +- Afecta: ingresos, egresos, adquisición mercancías, IVA balance, IVA mensual, resumen IVA, resumen ISR, IVA a favor, regímenes del periodo, chart ingresos/egresos + +### Métricas del Dashboard +- **Ingresos del Mes** — Por régimen del emisor, filtrable +- **Gastos del Mes** — Por régimen del receptor, filtrable +- **Adquisición de Mercancías** — Egresos con uso_cfdi G01, por régimen, filtrable +- **Utilidad** — Ingresos - Egresos +- **Balance IVA** — Causado - Acreditable, por régimen +- **CFDIs Emitidos/Recibidos** — Contadores +- **IVA a Favor** — Acumulado anual e histórico (5 años) + +### Métricas de Impuestos +**IVA:** Trasladado, Acreditable, Retenido, Resultado, Acumulado Anual — todos filtrables por régimen con `trasladadoPorRegimen`, `acreditablePorRegimen`, `retenidoPorRegimen` +**ISR:** Ingresos acumulados, Deducciones, Base gravable, ISR causado, ISR retenido, ISR a pagar — todos filtrables por régimen con `ingresosPorRegimen`, `deduccionesPorRegimen`, `baseGravablePorRegimen` + +### Conciliación — reglas importantes +- **Recibidos:** excluye PPD (solo PUE y pagos) +- **Emitidos:** excluye PPD para todos los regímenes excepto 605 y 616 +- **Facturas tipo P:** usan `monto_pago_mxn` en vez de `total_mxn` +- **Auto-conciliación PPD:** cuando una factura P con `saldo_pendiente = 0` se concilia, la PPD relacionada (via `uuid_relacionado`) se auto-concilia con los mismos datos. +- **Score cards:** PPD auto-conciliadas tienen `montoMxn = 0` para no duplicar montos + +### SAT — Sincronización +| Archivo | Qué hace | +|---------|----------| +| `src/services/sat/sat.service.ts` | Orquestación: sync jobs, chunking inteligente, saveCfdis, saveMetadata | +| `src/services/sat/sat-client.service.ts` | HTTP client SAT: query, verify, download. DocumentStatus 'active' para XMLs | +| `src/services/sat/sat-parser.service.ts` | Parseo de XML CFDI + metadata CSV del SAT | +| `src/services/fiel.service.ts` | FIEL: upload, encriptación AES-256-GCM, validación | +| `src/jobs/sat-sync.job.ts` | Cron 03:00 AM: sync automático para todos los tenants con FIEL | + +**Cuatro modos de sincronización:** + +| Modo | Cuándo | XMLs | Metadata | +|------|--------|------|----------| +| `initial` | Primera sync del tenant | Sondeo → bloques 3/6 meses | Bloques de 3 años | +| `daily` | Cron 3:00 AM (todos los planes) | Últimos 7 días | Año fiscal completo (ene → hoy) | +| `incremental` | Cron 11:00/15:00/19:00 (solo plan Enterprise) | Últimas 8 horas | Últimas 8 horas | +| `custom` (rango personalizado) | Botón en UI | Directo si ≤6m, bloques de 6m si >6m | Rango completo | + +**Incremental Enterprise:** Reduce latencia de CFDIs nuevos a ~4h en horario laboral. Ventana de 8h cubre el gap 03:00→11:00 y solape con disparos siguientes (dedup por UUID). Requiere `initial` completado previamente — el job se omite si el tenant no tiene backfill. Ver `docs/plans/2026-04-13-sat-incremental-enterprise.md`. + +**Flujo de sincronización (initial):** +1. Sondeo: metadata del rango completo (2 solicitudes) → determina volumen +2. XMLs vigentes: bloques de 6 meses (≤15k CFDIs) o 3 meses (>15k) +3. Metadata vigentes+cancelados: bloques de 3 años +4. XMLs se guardan en disco (`data/xmls////`) antes de procesar +5. RFCs se upsert en tabla `rfcs` con `rfc_emisor_id`/`rfc_receptor_id` FK +6. Metadata: inserta CFDIs cancelados sin XML, actualiza status de existentes + +**Retry automático en timeouts (con políticas por tipo):** +- Poll interval: 60 segundos, máximo 45 intentos (45 min) → `MAX_POLL_ATTEMPTS` +- Tiempos de retry **absolutos desde `startedAt`** (no desde el timeout): si la sync arrancó a las 3:00 y timeoutea a 3:45, el primer retry queda programado para 9:00 exacto, no 9:45. +- Política por tipo (`RETRY_POLICIES` en `sat.service.ts`): + - `daily` → 2 retries a T+6h, T+12h + - `initial` con `isCustomRange=true` (rango UI) → 2 retries a T+6h, T+12h + - `initial` bootstrap (sin fechas) → 3 retries a T+6h, T+12h, T+24h + - `incremental` → **0 retries** (próximo cron cada 4h cubre el gap, dedup por UUID) +- Cron horario revisa `pending` con `nextRetryAt <= now` y reintenta. Tras agotar retries: `failed` con mensaje "Fallo conexión SAT, vuelve a intentar con un rango de fechas menor." + +**Reuso de `satRequestId` en retries (`sat_request_ids` JSONB map):** cada vez que `requestAndDownload` necesita un request al SAT, primero busca en `job.satRequestIds[kindKey]` (kindKey = `${requestType}-${tipoCfdi}-${dateFrom}-${dateTo}`). Si encuentra uno, llama `verifySatRequest` directo: si está `ready` salta polling y descarga; si `processing` continúa polling con MISMO id; si `failed/rejected` o lanza excepción, fallback a crear nuevo. Esto evita agotar la cuota de solicitudes activas del SAT en reintentos. `satRequestId` (singular) sigue actualizándose al último creado para backward-compat de queries que ya lo leen. Persistencia atómica vía `jsonb || jsonb` SQL en `persistSatRequestId()`. + +**Pool management:** `ctx.getPool()` (no estático) para mantener `lastAccess` fresco durante syncs largos. + +### Facturapi — Emisión de facturas +| Archivo | Qué hace | +|---------|----------| +| `src/services/facturapi.service.ts` | Facturapi: organizaciones, CSD, clientes, emisión, cancelación, descargas, logo, color, envío email | +| `src/controllers/facturacion.controller.ts` | Endpoints: emitir, cancelar, PDF/XML, timbres, búsqueda RFCs, conceptos previos, datos fiscales, personalización | +| `src/controllers/catalogos.controller.ts` | Catálogos SAT: forma pago, uso CFDI, clave prod/serv (con búsqueda sin acentos), unidades, etc. | +| `src/routes/facturacion.routes.ts` | Rutas de facturación con tenantMiddleware | +| `src/routes/catalogos.routes.ts` | Rutas de catálogos SAT | + +**Modelo Facturapi:** +- Una cuenta Horux360 (User Key `sk_user_...`) con organizaciones por tenant +- `tenants.facturapi_org_id` vincula tenant ↔ organización Facturapi +- API key de org se obtiene via HTTP directo (`/v2/organizations/{id}/apikeys/test`) — el SDK tiene bugs con este endpoint +- CSD (Certificado de Sello Digital) se sube por organización (no requerido en modo test) +- Timbres controlados por `timbre_suscripciones` (50/mes o 600/año, pool del plan) + `timbre_paquetes` (compras adicionales, vigencia 1 año). Catálogo de paquetes vendibles en `timbre_paquetes_catalogo` (100/$200, 1000/$1400, 10000/$8600 por default, editable por admin global en `/configuracion/precios-timbres`). Pool mensual se **resetea a 0 al final del periodo** (cron diario 2:30 AM via `resetExpiredMonthlyTimbres`, no acumulable). `consumeTimbre(tenantId)` en `$transaction`: primero pool mensual, luego `TimbrePaquete` con menor `expiraEn` (FIFO anti-desperdicio). Compra self-serve en `/facturacion/timbres` (owner/cfo): `POST /api/facturacion/timbres/paquetes/comprar` crea `Payment(kind=timbres_pack)` + MP Preference con `external_reference=timbres-pack:{paymentId}`. El webhook MP aprueba el Payment, llama `activarPaqueteTrasPago` (crea `TimbrePaquete` con `expiraEn = now + 1 año`), y dispara `invoicingService.emitInvoiceIfApplicable` que ramifica por `Payment.kind` (concepto "{cantidad} timbres adicionales" vs "Suscripción {plan}"). `Payment.kind` enum (`subscription | timbres_pack`) discrimina flujos en un solo modelo. +- Al emitir: guarda en `cfdis` con `source: 'facturapi'` + `facturapi_id`, upsert RFCs con regimen/CP +- Envío automático por email al receptor si tiene email configurado +- **Cancelación:** botón en `/cfdi` para facturas con `source='facturapi'` y status vigente. Modal con los 4 motivos SAT (02 = errores sin relación, default; 01 = errores con relación, requiere UUID sustituto; 03 = no se llevó a cabo; 04 = incluida en factura global). `POST /facturacion/cancelar/:uuid` con `{ motive, substitution? }`. El estatus en BD local pasa a `Cancelado` al confirmar, pero el SAT puede dejarla en "pendiente" si requiere aceptación del receptor (copy en el modal lo advierte). Distinta a `DELETE /cfdi/:id` que solo borra el registro local. +- Personalización por organización: logo (PNG/JPG ≤2MB) y color (HEX) + +**Catálogos SAT en BD central (9 tablas):** +- `cat_forma_pago` (22), `cat_metodo_pago` (2), `cat_uso_cfdi` (24), `cat_moneda` (7) +- `cat_clave_unidad` (26), `cat_clave_prod_serv` (52,513 — importado de phpcfdi/resources-sat-catalogs), `cat_objeto_imp` (4) +- `cat_tipo_relacion` (7), `cat_exportacion` (4) +- Búsqueda de `cat_clave_prod_serv` soporta búsqueda sin acentos (regex PostgreSQL) + +**Tipos de comprobante soportados:** +| Tipo | Secciones dinámicas | +|------|-------------------| +| I - Ingreso | Conceptos con traslados/retenciones por concepto (IVA, ISR, IEPS, impuestos locales), exportación, serie/folio, condiciones, descuento, objeto de impuesto. Factura global (periodicidad, mes, año). Autocompletar receptor desde tabla RFCs. Búsqueda de conceptos previos en facturas. | +| E - Egreso | Igual que Ingreso + documento relacionado (UUID + tipo relación 01-04) | +| P - Pago | Solo comprobante (tipo+serie/folio) + receptor + complemento de pago. Autocompletar UUID desde CFDIs PPD pendientes del receptor con saldo. Desglose IVA del pago (base, tasa, monto). | +| T - Traslado | Solo comprobante (tipo+serie/folio) + receptor + conceptos sin precio/impuestos. Unidades filtradas (sin servicio). | + +**Recomendación automática de retenciones (tipo I):** +- Emisor PF (13 chars RFC) con régimen 612, 626, o 606 +- Receptor PM (12 chars RFC) con régimen distinto a 612/626/606 +- Unidad de servicio (E48, ACT) +- → Auto-agrega: IVA retenido 10.6667% (2/3) + ISR retenido (1.25% RESICO, 10% otros) + +**Cliente extranjero:** +- Detectado al escribir RFC `XEXX010101000` +- Auto-llena: régimen 616, CP del tenant +- Campos adicionales: Tax ID extranjero + País (selector con ~50 países) +- Backend envía `country` en address, Facturapi agrega RFC genérico extranjero + +**Factura global:** +- Toggle en tipo I que auto-llena receptor con PUBLICO EN GENERAL (XAXX010101000, régimen 616) +- Campos: periodicidad (day/week/fortnight/month/two_months), mes (01-18), año +- CP se auto-llena desde datos fiscales del tenant + +**Datos fiscales del tenant (tabla `tenants`):** +- Columnas: codigo_postal, calle, num_exterior, num_interior, colonia, ciudad, municipio, estado, telefono +- Editable desde Configuración > Domicilio Fiscal +- Se usa para: CP en facturas globales, datos del emisor + +### Calendario y Recordatorios +| Archivo | Qué hace | +|---------|----------| +| `src/controllers/calendario.controller.ts` | GET eventos fiscales + recordatorios custom, CRUD recordatorios | +| `src/services/recordatorios.service.ts` | CRUD recordatorios en tabla `recordatorios` del tenant | + +**Recordatorios custom:** +- Solo fechas únicas (sin recurrencia) +- Público (visible para todo el equipo) o privado (solo el creador) +- Admin y contador pueden crear/editar/eliminar +- Estilo morado en el calendario +- Se mezclan con eventos fiscales generados, ordenados por fecha + +**Recordatorios automáticos de e.firma:** +- Al subir la FIEL, se crean 3 recordatorios públicos: 60, 30 y 7 días antes del vencimiento +- Prefijo `[e.firma]` en el título para identificarlos +- Al re-subir la FIEL, se eliminan los anteriores y se crean nuevos +- Solo se crean si la fecha no ha pasado + +### Documentos — Opinión de Cumplimiento +| Archivo | Qué hace | +|---------|----------| +| `src/services/opinion-cumplimiento.service.ts` | Orquestación: decrypt FIEL → Playwright headless → scrape PDF → parse → guardar en BD tenant | +| `src/services/sat/sat-opinion-login.ts` | Playwright: navegación portal SAT → login FIEL → reporte opinión | +| `src/services/sat/sat-opinion-scraper.ts` | 4 estrategias para extraer PDF base64 del DOM Angular del SAT | +| `src/services/sat/sat-opinion-parser.ts` | Parseo de texto del PDF con regex (RFC, razón social, estatus, folio, cadena original) | +| `src/controllers/documentos.controller.ts` | Endpoints: listar opiniones, descargar PDF, trigger manual | +| `src/routes/documentos.routes.ts` | Rutas con tenantMiddleware + requireFeature('documentos') | + +**Cron semanal:** Domingos 4:00 AM (`0 4 * * 0`). Descarga opinión para todos los tenants con FIEL. Limpia registros > 6 meses. + +**Seguridad FIEL:** Archivos temporales con `0o600` en tmpdir con UUID. Cleanup garantizado en `finally`. Contraseña solo en memoria. Playwright headless. Timeout 3 min por tenant. + +**Almacenamiento:** PDF completo en BYTEA en tabla `opiniones_cumplimiento` (BD tenant). 6 meses de retención. Frontend muestra últimas 5. + +**Alerta automática:** Si última opinión no es Positiva → alerta alta en alertas automáticas. + +**Feature gate:** `documentos` — disponible en planes Business y Enterprise. + +### Documentos — Declaraciones Provisionales +| Archivo | Qué hace | +|---------|----------| +| `src/migrations/tenant/003_create_declaraciones_provisionales.sql` | Tabla `declaraciones_provisionales` con 1 normal única por (año, mes) + N complementarias | +| `src/migrations/tenant/004_declaraciones_liga_pago_pdf.sql` | Reemplaza `link_pago TEXT` por `pdf_liga_pago BYTEA` + filename (la liga de pago es PDF, no URL) | +| `src/services/declaraciones.service.ts` | CRUD + auto-resolución de alertas por impuesto/mes + purge 5 años | + +Flujo: el contador sube 3 PDFs por mes — **declaración** (obligatorio), **liga de pago** (opcional), **comprobante de pago** (opcional). Al subir la declaración se marcan como resueltos los recordatorios `decl--` correspondientes a los impuestos seleccionados (IVA, ISR, IEPS, SUELDOS, DIOT, OTRO). Si es tipo `complementaria`, también se resuelven los recordatorios `pago-*` del mes (la complementaria sustituye a la normal en pago). Al subir comprobante de pago después, se resuelven los `pago-*` del mes. Retención 5 años (CFF Art. 30) purgada en cron lifecycle diario 2:30 AM. El flujo manual de "marcar como realizado" desde /alertas se mantiene para usuarios que no quieran subir documento. + +UI: pestaña "Declaraciones Provisionales" en `/documentos` con selector de año, tabla mensual con badges tipo/impuestos y botones descarga/subir-pago/eliminar. Roles con upload: owner, cfo, contador, auxiliar. + +### Documentos — Constancia de Situación Fiscal (CSF) +| Archivo | Qué hace | +|---------|----------| +| `src/migrations/tenant/005_create_constancias_situacion_fiscal.sql` | Tabla con PDF BYTEA + datos JSONB (shape completo) + retención 5 años | +| `src/services/sat/sat-csf-login.ts` | Playwright: página pública SAT → popup SERVICIO → login FIEL (con retry `dispatchEvent` si el click sintético se pierde) | +| `src/services/sat/sat-csf-scraper.ts` | Busca "Generar Constancia" en cualquier `frame()` (vive en iframe JSF legacy `rfcampc.siat.sat.gob.mx`). 3 rutas: download event, popup viewer, response interception | +| `src/services/sat/sat-csf-parser.ts` | Parser PF+PM: labels key:value + 3 tablas (actividades/regímenes/obligaciones, agrupadas por "chunk termina en dd/mm/yyyy") + sellos | +| `src/services/constancia.service.ts` | Orquestación + `sincronizarDatosFiscales(tenantId, csf)` — auto-fill domicilio tenant (codigoPostal, calle, numExterior/Interior, colonia, ciudad, municipio, estado) y regímenes activos (matcheados contra catálogo `regimenes` por nombre normalizado) | + +**Cron mensual:** Día 1 de cada mes 04:00 AM (`0 4 1 * *`). Descarga CSF para todos los tenants con FIEL. Por-tenant try/catch — un fallo no bloquea al resto. + +**Retención:** 5 años purgados junto con declaraciones en cron lifecycle diario 2:30 AM. + +**Trigger on first-upload FIEL:** En `fiel.service.ts`, al primer upload exitoso (`existingFiel` nulo o inactivo) se disparan en background Opinión de Cumplimiento + CSF con `import()` fire-and-forget. No bloquea la respuesta al usuario. + +**Headless por default:** `chromium.launch({ headless: true })`. El fix clave es en `sat-csf-login.ts`: el click sintético a "e.firma" del portal SAT a veces no dispara el handler, por eso se espera a que aparezca `input[type=file]` (10s) y si no llega, reintenta con `dispatchEvent('click')`. Para debug visual temporal, setear `SAT_HEADLESS=false` en `.env`. + +**UI:** pestaña "Constancia de Situación Fiscal" en `/documentos` con último CSF expandido (identificación, domicilio, regímenes activos, obligaciones), historial de 12 con detalle desplegable, descarga PDF, y botón "Consultar ahora" (owner/cfo). La UI refleja `datos: JSONB` de la BD sin re-parsear el PDF. + +**Shape `ConstanciaSituacionFiscal`:** rfc, curp?, idCIF, nombre?/primerApellido?/segundoApellido?/razonSocial?, estatusPadron, fechaInicioOperaciones, lugarFechaEmision, domicilio (11 campos), actividadesEconomicas[], regimenes[], obligaciones[], cadenaOriginalSello, selloDigital. + +### Integración Metabase + +**`apps/api/src/services/metabase.service.ts`** auto-registra cada BD postgres de tenant nueva en Metabase para BI (queries/dashboards). Auth con session token cacheado 13 días via POST `/api/session` → header `X-Metabase-Session`. Funciones expuestas: `registerDatabase({nombre, dbName})` y `deleteDatabase(databaseName)` (busca por `details.dbname` o `name` contains, DELETE). + +**Integrado en `tenants.service.ts`** en 3 puntos: `createTenant`, `addTenantToOwner`, `deleteTenant`. Todas son llamadas **fire-and-forget con `.catch()`** — un fallo de Metabase NO bloquea la creación/borrado del tenant. + +**7 variables `.env`** (todas opcionales): `METABASE_URL`, `METABASE_USERNAME`, `METABASE_PASSWORD`, `METABASE_PG_HOST`, `METABASE_PG_PORT`, `METABASE_PG_USER`, `METABASE_PG_PASSWORD`. Sin `METABASE_PASSWORD` o `METABASE_PG_PASSWORD`, el service skip-ea cada llamada con un log `[METABASE] Skipping...` y el sistema funciona sin Metabase. **No expone routes HTTP propias** — es solo integración interna; los usuarios consultan dashboards directo en la URL del Metabase. + +### Frontend +| Archivo | Qué hace | +|---------|----------| +| `apps/web/lib/api/client.ts` | Axios instance con auto-refresh JWT + `X-View-Tenant` | +| `apps/web/stores/auth-store.ts` | Zustand: user, tokens, logout (persist localStorage) | +| `apps/web/stores/tenant-view-store.ts` | Zustand: impersonación de tenant (admin global) | +| `apps/web/stores/theme-store.ts` | Zustand: tema visual (light/dark) | +| `apps/web/lib/export-excel.ts` | Utilidad client-side para export Excel | +| `apps/web/lib/hooks/use-facturacion.ts` | Hooks para facturación + catálogos SAT | +| `apps/web/lib/hooks/use-calendario.ts` | Hooks para calendario + recordatorios | + +**Tenant-aware query keys:** Todos los hooks de datos (`use-dashboard`, `use-bancos`, `use-calendario`, `use-facturacion`) incluyen `viewingTenantId` en los query keys de React Query para refetchear al cambiar de empresa. + +**Temas:** Solo Light y Dark habilitados. Fondo Light = lavanda sutil (270 50% 98%). + +--- + +## Convenciones importantes + +### Cálculos fiscales por régimen +Los ingresos/egresos/IVA se calculan de forma diferente según el grupo de régimen: +- **PF Empresarial (606, 612, 621, 625, 626):** Facturas PUE + Pagos - Notas de crédito PUE +- **Sueldos (605):** Nóminas recibidas PUE +- **PM y otros (601, 603, 607...):** Facturas PUE+PPD - Notas de crédito PUE + +Los montos se calculan **sin impuestos** (total - IVA trasladado - IEPS - impuestos locales). +Los montos en MXN se usan siempre para cálculos (campo `_mxn`). + +### Convenciones de BD +- BD central: nombres en `snake_case` mapeados por Prisma a `camelCase` +- BD tenant: SQL directo, alias explícitos en queries (`rfc_emisor as "rfcEmisor"`) +- `CFDI_SELECT` constant en `cfdi.service.ts` define todos los campos mapeados +- Status vigente: `WHERE status NOT IN ('Cancelado', '0')` + +### Tenant provisioning +Al crear un tenant (`TenantConnectionManager.provisionDatabase(rfc)`): +1. Crea BD `horux_` +2. Ejecuta `createTables()`: crea `rfcs`, `bancos`, `cfdis`, `cfdi_conceptos`, `conciliaciones`, `alertas`, `recordatorios` +3. Ejecuta `createIndexes()`: índices B-tree + trigram (pg_trgm) + FK diferida para `id_conciliacion` + +### Tablas por tenant +| Tabla | Propósito | +|-------|-----------| +| `rfcs` | Catálogo de RFCs (id, rfc, razon_social, regimen_fiscal, codigo_postal) | +| `bancos` | Cuentas bancarias (id, banco, terminacion_cuenta) | +| `cfdis` | Facturas electrónicas (100+ columnas, incluye conciliado, id_conciliacion, facturapi_id, source, cfdi_tipo_relacion, cfdis_relacionados, saldo_pendiente_mxn) | +| `cfdi_conceptos` | Líneas de detalle por CFDI | +| `cfdi_descartados` | CFDIs marcados como ignorados por tipo de alerta (whitelist por contador) | +| `conciliaciones` | Registros de conciliación (id, anio, mes, id_cfdi, fecha_de_pago, id_banco) | +| `alertas` | Alertas manuales persistidas | +| `recordatorios` | Recordatorios custom del calendario (título, fecha, público/privado, creado_por) | +| `entidades_gestionadas` | Entidades del despacho (clientes/contribuyentes) — tipo, nombre, supervisor_user_id | +| `contribuyentes` | Contribuyentes con FK a entidades_gestionadas (rfc, regimen_fiscal CSV, domicilio, email_preferences jsonb) | +| `carteras` | Carteras del despacho con supervisor_user_id, auxiliar_user_id, parent_id (subcarteras) | +| `cartera_entidades` | M:N cartera ↔ contribuyente | +| `cartera_auxiliares` | M:N cartera ↔ auxiliar (legacy, ahora en `auxiliar_user_id` directo) | +| `auxiliar_supervisores` | Override 1:1 auxiliar → supervisor (editado desde `/usuarios`) | +| `obligaciones_contribuyente` | Catálogo de obligaciones por contribuyente | +| `obligacion_periodos` | Instancias mensuales de cada obligación; estado completada | +| `tareas_catalogo` | Tareas operativas recurrentes por contribuyente (semanal a anual) | +| `tarea_periodos` | Instancias materializadas con fecha_limite y estado | +| `papeleria_trabajo` | Archivos del despacho (PDF/Word/Excel ≤5MB) por contribuyente con flujo opcional de aprobación | +| `declaraciones_provisionales` | PDFs de declaraciones por (contribuyente, año, mes, tipo) con liga de pago + comprobante de pago | +| `documentos_extras` | PDFs libres por contribuyente con categoría y descripción | +| `opiniones_cumplimiento` | Cache 6 meses de Opinión SAT (PDF + datos parseados) | +| `constancias_situacion_fiscal` | Cache 5 años de CSF (PDF + datos JSONB) | +| `facturapi_orgs` | Org Facturapi por contribuyente (api_key cacheada, csd_uploaded, last_lco_rejection_at) | + +### Campos source en cfdis +- `'manual'` — Cargado por usuario via XML upload +- `'sat'` — Descargado del SAT via sync (tiene xml_original) +- `'sat-metadata'` — Solo metadata del SAT (sin XML, típicamente cancelados) +- `'facturapi'` — Emitido desde Horux360 via Facturapi (tiene facturapi_id) + +### Impersonación de tenant (X-View-Tenant) +El admin global puede ver/gestionar datos de otros tenants. Los endpoints que lo soportan usan: +- Backend: `tenantMiddleware` + `effectiveTenantId(req)` = `req.viewingTenantId || req.user!.tenantId` +- Frontend: `useTenantViewStore()` + tenant key en query keys de React Query + +Rutas con tenantMiddleware: dashboard, cfdi, impuestos, reportes, conciliación, bancos, calendario, regímenes, fiel, sat, facturación. + +--- + +## Setup local + +```bash +# Requisitos: Node 20+, pnpm 9+, PostgreSQL 16+ +pnpm install +pnpm db:generate # Genera Prisma client +pnpm dev # Arranca API (4000) + Web (3000) via Turborepo +``` + +### Bootstrap desde BD vacía (deploy fresco) + +```bash +cd apps/api +pnpm prisma migrate deploy # schema central +pnpm db:seed # catálogos + tenant demo + roles +pnpm bootstrap:admin-global # tenant Horux 360 (HTS240708LJA) +pnpm import:lista-negra # opcional +``` + +`bootstrap:admin-global` usa `tenantsService.createTenant()` con overrides enterprise (`cfdiLimit: -1`, `usersLimit: 10`) + eleva la subscripción a `authorized` por 1 año. Imprime password temporal del admin en consola. Configurable vía `HORUX_ADMIN_EMAIL` / `HORUX_ADMIN_NOMBRE`. + +**Variables de entorno:** `apps/api/.env` y `apps/web/.env.local` +**BD de prueba:** tenant `EDE123456AB1` → BD `horux_ede123456ab1` + +**Credenciales demo:** +- admin@demo.com / demo123 (admin) +- contador@demo.com / demo123 (contador) +- visor@demo.com / demo123 (visor) + +**Credenciales Horux 360:** +- carlos@horuxfin.com / (password en consola del bootstrap) — owner + platform_admin +- ivan@horuxfin.com / (password en consola del bootstrap) — contador + platform_ti (TI superset) + +--- + +## Producción + +- **Servidor:** Ubuntu 24.04, 22GB RAM, 8 cores +- **Process manager:** PM2 (fork mode) +- **Proxy:** Nginx con SSL (Let's Encrypt) +- **Cron:** SAT sync 03:00 AM, Backups 01:00 AM +- **Deploy:** `git pull → pnpm install → pnpm build → pnpm db:migrate-tenants → pm2 restart all` + +--- + +## Problemas conocidos / pendientes + +**Typecheck limpio.** `pnpm typecheck` pasa 0 errores en `@horux/api` y `@horux/shared` tras el cleanup del 2026-04-13. El proyecto corre con `tsx watch` que no valida tipos en runtime, por eso agregar `pnpm typecheck` a CI/pre-commit es recomendado para no re-acumular deuda. Ver `docs/plans/2026-04-13-typescript-debt-cleanup.md` para el inventario completo y decisiones descartadas (incluye por qué no se downgradeo `@types/express@5` ni se migró a `"type": "module"`). + +**Express version mismatch:** `@types/express@^5.0.0` corre sobre `express@^4.21.0`. La diferencia se manifiesta en `req.params.id` tipado como `string | string[]`. Solución aplicada: cast `String(req.params.id)` en los 8 controllers afectados. Cuando migren a Express 5, los casts siguen siendo correctos. + +**Facturapi SDK interop:** el paquete `facturapi@4.14.2` no declara `"exports"` en su package.json, solo `main`/`module`. Esto impide migrar el api a `"type": "module"` sin workarounds en cada `import Facturapi from 'facturapi'` (el default export se trata como namespace bajo ESM estricto). `tenant-migrations.ts` usa `__dirname` directo para evitar `import.meta` y mantenerse compatible con el modo CJS efectivo. + +1. ~~**Schema drift multi-tenant:**~~ Resuelto. Migraciones SQL numeradas en `apps/api/src/migrations/tenant/`. Se aplican eager (`pnpm db:migrate-tenants`) en deploy y lazy (auto en `getPool()`) como safety net. Para agregar un cambio de schema tenant: crear `NNN_description.sql` en el directorio de migraciones. + + **Términos y Condiciones:** fuente legal = `docs/legal/Terminos y condiciones.pdf`. Pipeline: `pnpm legal:sync` (corre `scripts/extract-terminos.mjs` usando `pdf-parse` v2 con `PDFParse.getText()`) copia el PDF a `apps/web/public/legal/terminos-y-condiciones.pdf` y genera `apps/web/content/terminos.ts` con el texto extraído (cheap insurance para indexado/búsqueda futura). Página `/terminos` (pública, fuera de `(auth)`/`(dashboard)`) **embebe el PDF con ``** — usa el viewer nativo del navegador (PDF.js/QuickLook) y preserva fidelidad total de formato (tipografía, tablas, listas). Header con "Descargar" (download attribute) y "Abrir" (nueva pestaña). Fallback dentro del object para móviles sin inline PDF: botones centrados. Register checkbox obligatorio con link a `/terminos` (target=\_blank). Workflow para actualizar: reemplazar el PDF en `docs/legal/` con el mismo nombre → `pnpm legal:sync` → commit los 3 artefactos (PDF fuente + PDF copia + `terminos.ts`). + + **Security headers (`next.config.js`):** aplicados a todas las rutas para proteger contra clickjacking y otros vectores. `X-Frame-Options: SAMEORIGIN` + `Content-Security-Policy: frame-ancestors 'self'` impiden que sitios externos embeban Horux 360 en iframes propios (preservando el `/terminos` que embebe un PDF del mismo origen). `X-Content-Type-Options: nosniff` previene MIME sniffing. `Referrer-Policy: strict-origin-when-cross-origin` evita leak de URLs completas al navegar externo. `Strict-Transport-Security: max-age=31536000; includeSubDomains` fuerza HTTPS (ignorado en dev). Si en el futuro se necesita embeber Horux 360 en otro dominio propio (app móvil híbrida, portal partner), extender `frame-ancestors 'self' https://otro-dominio`. + + **BD central**: usa Prisma migrations en `apps/api/prisma/migrations/`. Baseline `20260414152220_initial_schema_v0_9_2/migration.sql` consolida todo el schema acumulado hasta v0.9.2. Para futuros cambios: `pnpm prisma migrate dev --name ` genera SQL versionado. En prod: `pnpm prisma migrate deploy`. **NO** usar `prisma db push` en prod — se pierde el trail. +2. **Nómina (pendiente):** Tipo de comprobante N no implementado en facturación. Requiere complemento de nómina con datos del empleado, percepciones, deducciones. +3. **Carta Porte (pendiente):** Complemento para facturas tipo T. Facturapi lo soporta en Beta. No implementado. +4. **Notificaciones por email de alertas/recordatorios:** El sistema de email existe (Nodemailer + Gmail) pero no envía alertas/recordatorios automáticos. Solo bienvenida, pagos, notificaciones admin y facturas (via Facturapi). +5. **Catálogo c_ClaveProdServ:** 52,513 registros importados desde phpcfdi/resources-sat-catalogs. Si se necesita actualizar, re-importar desde la BD SQLite del release. +6. **SMTP local:** No configurado en desarrollo. Emails se logean a consola. Configurar `SMTP_USER` y `SMTP_PASS` en `.env` para envío real. diff --git a/CSF_ejemplos/9f877571-c527-445d-979a-9ab99479d851.pdf b/CSF_ejemplos/9f877571-c527-445d-979a-9ab99479d851.pdf new file mode 100644 index 0000000000000000000000000000000000000000..51000a9a365779e33d720dfda874b3bfa5cb0a0a GIT binary patch literal 141156 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 z zXmXXnh$$FC^37G>AM{S6_5*WJ&Jnj2`}*{9V9d|NVCP7IQrsDE#PQf!&-_*{j07|G z7bZt{w71htU4}EePe(4;LTE&qV3iOTbFwY zt-V4RIw$Wa3iR(~u~Y&jF#GD)%MQoY^0^A5QABG`zT~j{ z6}uEcXKS+8mDBbo!mF5g%mJ&WIK8>7?4b|{OjuWybY=Of)2Xd;SYt`h(^F9$6GKCg zzSuq6J+Ft9b@GjBv6h;CVg1W?x07(NbAEMjiC6{ZgI%4v|H$#xW5Lddq^&*o+QKC+ zF;#fQSW$mR>J^7?U$RRYq4I|+eXrize>AFc5GI$hYGcl>us`T97VPjv^|dK(-+X^E zxmgM$#00&)l~vKvgE)|T4A`j)TJ^$)(^owk`?*HBLR?kow`7w`V2aPRw7Bbg<7@Co z`e1&@YR5BnlYt%2y#6++5tC3l3$A~;$T_NF7?$i54>0S>gM$ZfjBK7XEnI%~VEF}< zb@1TwLwa(qYq|J^t)q;WHC<($qM?asv;v9I!Jps8Q6*RSTu8)dsOnk zh{!19(h}lsuUc%M+7fx{PK5V0Qaip}d*iOWyY#ps?{7^*^h%ug7SU@&oc^NXghQmPzY&)VF~~{3K>(*?_zco$+(RQ!2kvsyAYFC#+-tJ z*W7`BWQN5-Qi7RQL|B(I5qmac-u@SC05ZmHa}$XbK0u>rnCJAbuMbY2N5rr>O^2xA z0IZ%luRN1PI3U0eh=9%sIeGoZ|N7WrI%sAE8rc8$r-wiMCetTU#t})m;=@)ez;&IW zJIN|DAPUSI!NP2URH)IQ)RFTm+;=$b{;!YrPyZCw>zO&k0UhyZJ*A>*^g~mJ0oClx> z&JqBc&AYPQL9fzcsJUbhZm^4)yd-zO{V&?s!?-~LXw}-M#nBC(q%(D0;B#xio<|W1?v8=mbG8BB9!nG` zwI~6+jqpY4kWjJE4q&D5XKaHBAJCNb;h;m%+eDt7;3?}Op0y2mW?j&?u5la%d_GT- z)Q=7h(gE0+7+F9M&KrvsE!e(&%dXwqf7rDCz4s<*ITlvDJ*U3D4lHerM!jw8mdAjX z*I#>?OeQ~=I1C%%avU8Uo``q8`toy;NI1$?IJO51*s+L3CKI(-n7(g=M?!Ta=b;z- zs=ln$xS%6Jt%yNRNji6O{Wo8KyKU#~$Xe+DrR3iI6Dv0^x3*rga*6ZJTeWhId{WQO z(IP}599*k zS#iHaX?z;611W8K3p_pcu3ENm?TS<0r4nXaZD^R=<{hi7t-oAiv*XmoyumJk;UP9Q zotW2^n;YcpvdQ}M&#bN2+dBr8lj!X2o8svk$WnNyV*8K3Dwe{i^i|pMlXLVCW zYOV3fV26WfbmG45;@fwRu2}c2we`}~D_!rVlGT!t#?-L$JAe4r`s?Li*qyt2W#h)% zX=A~TQ77V7g~c4%u=-#+AIVz0TRPu3-_Enw90} z=tQ*N;OHD)T{`%1sZ@hP&L)#n&tKTP;M+yk)+@H`c8e+M&>*NrQ0p6ZcGaeDKeb-C z>hm4mKBpF(i7H5M8|-O_JmIi*!55!dZ*sCbvES9>(pa#=GpJO|hF1T*_FsLu2>z3>vnkBU;oGkluG)VnG^L(L0eJ1Y)6!K-tgXLYzxs4wV2ehf8}k@N z4MxofJ;Tjo`*+L1^ekPm|Ds=}-}dV%Wyu{%1*fSw1lT{prHxwm+Va&Fbch_)R&nZa;2b4lZD`@504Po44GK9}9Lw zoP1J{o&8#HYd^QPUbn?5E;}|nAvJ7!=(s>Xi~L-HNzDk3YYZ~AjKXXQ;?axMGJw8i zI5RMUAZoRoq1Q2+3fv{bB}!qve7Hy=Xk&JzG~`^?s(Uob&bE^4E#-mjW!^1?SMq{3 zvMJuxi95?<_7w!Lrw3j+V!7 z&ko)s=Ts6_PDz51KSZl*Ne?X)v$&o z?ld9l^ANc{20c=jU3}m59BApW@5oKC?LFdQcP!Z}RK^vLCiviF`UVl5ODV0d$uFxX zRSb}+8j+I7pF#9UYh^=CK^eZ4a_CH0?*<#q(ua`50IhqYUZoS!IFz#bn!>W$g7VIa zMhS~&fCC~DLCg*(oMO1MzO^7MAv++lGC8LxGC9jXsye$^F(Nh*o|)-%QQXaAQ0TZF z^xFK=?4anxo4&chv5f`g!oDFy11qzb`X~NCFs)LPAD$R;&OP|-jo>pkLeAVsboXs9 zt;5VzXr>i_8F~FfG45WVLFk$5!52J=<1+;G;ZaU8BT*m~_kRIAC1vs(NEIpfLqbpA zh`e;CJUM5uwMVDW4)?RF3(HLNQ&_GN$q8aIu(}L*ESyH7qSaHtxwZLal(GgXgAb$i z@E9v@{}7mP;@sx)I!yO#TC+b7?BEpB0KKF8Z)80O5XT1S&6xr}!=~Cs!Wg#oq2t5P zrV#F)^#IO<{C~FoK^YB*>3$mk2)`O$iu-FKrmTwuEpglga81}Vwi86e;hjWGT^I1| zx&WX+Kx(xCdlVOpiW%%oj4#X)YfoE$=gm2Y=(uvJ7O-Oxi#$g38ckPT9%W#J_ecp2 zHJjewQ{KV_&lUMFxh5kvIw~o-hBAbLXL}c=B0f3F-#ZvP8i}4SQ49ct^H&>7-%&w7#wcXS@ z8j}W7hfo+$MXgFoA@s?sYZmAgdKhvy(oLJ;Jn)9H2QUR%p!yaFWQZLi;8f$VwqC)-sV^T9~yEt;(9#Hi4)~BVz z>5UD_XzOaqY3ygyB^sG z+}}ScIlH1Hi;9vh?{}vRZ><}NkU{~YL7G z)mPHpl-5{yt+mXnzcs$I@_tWkq(;T(vfDYde7U5(qtaK-uj{VxNW8tMGP*4)qmRWn2?*$UQBIZKc<{>m$^1CWdMH8k38HR#wGqsE*!L7`8<& z9>nS3h;k+9D4rl9f?yOUUggD9L4CBEy1{75={Fh)XN_?bSP+p!L}SE!BJ!Ys2?2L7 z6fv_4W2WLY7?Jb?x`qifM4}C0_nSN@sJT#xsoDwwwTRM^=A))6k_cwTMkdD-aU3tQ zf|)6Xji%D0ri!7cxpRW1+Ej>fG(OMFbUZ4iqAbK-)LgdHJXe^hD$K)R$7q5`59S0l z^?;7?sS#KQGgmJ)7x^KS!pu{Knfo)Hj^7iav?ie*(FtmriwE;*Mpk%K({Lr9%f zKXk{eewb;$H!J_5Rx1b6@29r+4@-ZeEBuBc8)0-L-%F!OmE(PG z7Wo|r?tC!9+8~_%n)rKPHKaPWlwQs8Unl2vAqEACqB;$qQXDcwNv2lDD)~LAZm=rv zOb;cwhaBBm8%fS}tIoPrpLe@F<7{`87ptpSqvA3;tNMB>mBL;|TSimiIcoV$N{Jh@ zEwryGQ^u)j$vtmSb8seJJ!;@prk-m}+fg02qb7bUtE&edsJ1PUR`D_wE zC=(K|Jr<#aO~E;(pIsN|n_6eUFb#$syO%MQpd)y?uEzBobUL8OU~&|W$z{WJ9U5ZV zu3h!eZ`{dI?8$ zf@sO$=631L$GMYUwOzYLA(xMRdGQ#qV<{?X5sO&FA{McTMSi-N2$Uh85~39i*QWWb zqSs%mNH|O>_7HMAF$}?TQ*?SAr+R5oJQA(-g z+#5VjyH3%=Xisjh@hXlx&|G+%T79#l!MmlvB<9|KNsv^5}p2~&3;p1+}Z?HbsoEg;mJENP;K|s6phg+ z(HfQ=Ny77Vf_@&@0o%G0u?C`$lsUL@jG!o@XAaWy^K!z&L$A7BS-WP{zutZ4m4E#G z^;chhu=5hpRdh%{!Gi7O{v$EMk$LCZ+5y}Kr0keb|46(Hp{XkTN3m$!xe zLa?*)P?Uin%P`S(JR<1mhN1?&UaQrr#bRN9e;*UrS&@Pgcq`iMJ!?wi&*4$EGm_h z-kP7^N|%XLPi}3I-#}1PBpa-)VqnvoE7Q)FMQ&^@y4Y72*j^Eis-?if8ez1yRs@KK z>U-NVnkplu{7!>Gt`IbIwIs_$)Si}5c28PG>WQX`fRck<- zK!p5V+d`!<{mB${2NE1%QBbWP4<>!GN zprA;npR(!>?xP8wx^6=5wI^?Dk3|w-=dE2tRh^)p=p=fut#XM#jCh?{f&<`*10Q!^ z#@~_0&%xNVpkF&r4zuLmS;QhgOTZUX(pOs8)JPo|mk6zq4EK}nly(a_zfA#lgHkFa z=V$nOdEdNw)6dT@r=XM}(HI^)AaqJGqv&2{U3agkO1&0EXw_i>K{p?jsGQo?k5EnF#n|Kz>o zn)q;hxc6?}j7e{1ux0CuKgjIvmW-0)J(XxsLw|K>Sz9MZ z_~gbCUKdy-K8EC(LZGQt{kGqE2K;m)f-VA2J5iPRnk^+zPk}q%el>c?FB|mLnX&` z)!ZAQXUPN&7%DenhCWKJh*>gwu#5@Z04c|0DNG@#XZ z1zKBMi;Ig#_o!5=jEoGCNYvNY2M#?R>=@yzcs&~z;*Jm^lgU6QV5h-g$j{Gb;^oso zV`5?%3(l0)l|G}HH|M|sNiKpBvR&d9~{>x7m zEH%l|08;rNkI@)?;q!kkUcBgQeCIZoOQCH>?E~Z3pi^=N>(hKZT(@mr0~&n4VgJE9 z%~XkYgx^-2dhev;)}>1qg9~rie&l9yb!Jvr(2l>){e0w>4QoJvo_~@OP7AVY}r?vcAxPrZ10ll zM)jz_{r&z`?|<@@S^EX=zxmGA9}fAY)WzO9wP*GB-!5JH<(dsUkKK$f7?SH0tg3)J z*S2li44Zzpbm>0(s}XsvgQ5rh{hkFbzckO7g8WAAFY1TaU!R}t8xD+~pJMnc5M6!5 zo`9T^Zssq#x>=MwW1+K09<6k6vKDv^T<^HNoQ7XU11;fqR8@t?q_T~Y|2J;*|2}!t zoGS=y3+G~a(|cYO#RV+JfBT~}*LMe1>SOCqhKP$8&mZ%DyVwOT=I zs|jc)U2Q85Mo>9`3jLjhLUuWaR?=8}fl_o;C1=1gEr`a5sw+}Ywxl1di`!iqzM(nQ zsWRh=5g;ABYA8<3`~ZVPb05l+UAli(o z>gDB?oSY2y0AR9v_iiJOIP`do&Zv)r&>S4f%E|(WfM^4H9@Phyy~iVpMo$4plai9| z-n~mSendCol+m$_?$6H729suMYrA8|4z*g1;6ijeJUr~`>I&wBP#)|ASn}hKKc>^^ z<>lomDJfuoW@ctrR~OMU_|#}LW4&77+_&C(YyJB5;J3T7vXZ!TbaZr4Q4ujfa0pBS zG5Sbc7h(pP75*KU(5URGr|vU;8d# zfF~dSf~zj>ZlsTUG5evN^zOzKm+ymGM<(I&MaA5%0{1VM9*?OYb0D-*sf-MU5j^}` z^FtDo5(?UYJq|1ZM&rFNTrXa9bc@V%v-2G=3UDt9!fzxO_^v!z#xVyK@#vKNkPATt zUBe;(A_c>Bx1)}H`1!rQD8Fm5sX5g>0)Qv=sxp~ddr;u(@%X5w>( zH5hbCl}4i-Q#4es)#@~wvGaz4$D~H7(rV>0ka_}`R3sL_8!C~jbqIJ` zqXxZBqflv83Yk!$)&jc$m~H0!D^;j<{N@g#BVB#x8Vu@x&e zZQ1ITRIz$*R_~+=Nl_w2QY>OGAi)Oq-b51Yy>}9rL&iP*V}KDyXTDyv*l|rE;zAr8An4Zwn*k zA?vln-31L9wmoe7*4%qd`SuEN|I7rqyKiH}G)Y9v(c710wtdF;r&tr_PkSw}nB*NbD0 zfZ~NNOD`RGm0y!%V`I;qI|mX&C=`OF6%`fWz@I;Vp2OjQ&m12gzjyCmaHnN5*~5nq zLqbCC+_^J7J?-r53@$f=!GIoi`}XZmKKaDm-5uP1e}8`)8yoQUAr6@L_|dF+qVxg0kK1y0g)k--rio?Hqc*?7EEevY;%bV_a!$s7v93%fx3fw zf~+8u_V#wT3M~oE2+9lU3o(?sfIjavr<(I!8wO~W*I#ushe<^adu~yYt&%Sn@%Aj z4~{~$gCPG2p~a59>+x-OLm#=-5n7Wc$M4Ne3*L3W*=hCp)FDdjhy)Y)IX;^Yy!FOU zSFQSR%h3n1B@+ZFkddswg5xu@lG4Ju3ok3?f2h4_%N+a%va2C z8~sDBezI}Zs()Fv>ht4{tXd9#qKcF8;A6MW-Weq|v~1Ap!2?&Bf0dGR%R3_vc{k1t zwAAt18nG(>@~x0;OR+OLP|`i!JHFsjH+r27OU}w0k}70k zaJ9)}48W7_Nhx7Dy@J?*qQ{HfLQ>-j+83+ZmYZ9ekifC1JfBlj$#V5C$h&^@*KfbG zYSnKxesw3ZeT>lQiP5&a+fIAm`*_u=AN}lC+i!)IkIzXUer+Yo(Z3+b_Si3MLvo5s z+d`SPpKe;U>IbV-M>2dtN-)A{^oyt@B2Ua=M8RQ6Ympq6^IU()zaX#|)2UP#w;24`c zLCRsM2nrNhJo-? zJD}0PfRFc{_Q|E{`y#O;=xeBsaSkXhx^-gp&)!|N>epYKx*6Iw3hcdQa3#IgE@ozC zhB4b?W@cs{^O%{LnVFe+%*^(fnVFfH*}nVzzH?5ha`PiMm8zstQA@Q&y<1OrYe~J9 zv=(~?*7fggXU-ek3e}3Q%PQAqUD%%l8OXvPLm48uQOJE|8-!{z$)^mNbbQRdEQX;G11Bwe!9a8V-^d4}PnYYvVt;kqzCWMy1M;wS zbae6si8#YJ01D*pbHDMJ@&^%;KyG(wM|jOpDG{!YKHs17U1kvS#PadG{l8?sUcK2U zfs#4s;<_9;KAg`FLw&l9eum&|=qr^5GsWrv$+HYwNoFD8K z<=3o=bladlzLJ$l6cZ!vv(a4$`58b8elZJ0W)I8czWZqCyIM&Pv3T6km^S)lW4YYw z{%WgPoQy)2x|oOz-mFSSU*zO;UtKjPM*Pv&iKT;6m6PeV_%_7)K0FaovKLZ*3#T6X zF8|rTH(%etti+V?YvN_MZgjUiKBXif;m#@nt;SK%{PmR=1h|xLm4c6?xTHdwDE=q2 zRecL@|MF^;fiCUQ^5JU<8TcYSr`H{sL1-?%-RDb*o~AW;vvtP-j>ZNzUA%L%3Gd2{ zZAFXK3S94S4QI#^>x8CFDU&k4_DN^%Dh~dM*X2ZSl~ybs?>=S7LZn_^kVj z)g*~(q!HOjkr2zdy&PS{>O|B(m=n-PElWxK&JVj|ZYj*iNfC0_+?4><;OpS3gw$cD zc17M$BpTQ(p3|4TlG^^voGOR1F{f3C{7aY!6+V^porl-gjbeTCl2(h4^A|C5i+h@F z1Y+)a#46tRNZGGzkHj35g;voa3g&TI%#@ooW^V%gU#>rRelYR!l5kUVU3528^ly%s zlo$BcR^;bQ()quqqh%j-9-H6(UdOB99bbMOr={?QaZt`|Q|8^cZ7!Lci@3PJYweMz zhoCSug4Zyjx45p9g$s|6o0^&mB8bDmoerCpsm>%GT#zylU)WMj#X`Yuz~WDes98Qc z=0Fdxo`?zvIQlBTn^C57M+co3?jkFnYCc}ZcaO_XRM}!)(9_jQM#7oYlpsLGOo+=| z9Uy7qbW+DvcCEU|T6z@c2ufvtxHSg-RE>90w-VN%qQai(+3yF%iixS@YEl!ENqXDv z^t~I1-q}0Lim^ZKnveUO!Ng6|!^6c%rZq6~yem_GQpkK8skAP?7HN&2 znT&1JIF~+io`-u`M+fJY(q_gijk4qT;P#{$5jJ9MOm4j5BnRD#Tlyj{)zB{%1YeLK zpb+`)Z3tq#f|<}IbrqAbWzje*F&y#%qD_!uz6{J_wNVlAmW2ymk1jJsy3{y9qufc2 zZr2CnTwy-J9K?$FsO<;|Sz^M_!r2uro{q;fS8Yr7YbO zX?sx-n3r1^8xs>q02B%Vw+q|?%*#p~#iug*gDVnuTn}I#7#oX7Nfn0+R@_V!>iZJv z3q+6KdF%cDG-|{&!{_4SqN}T$hxV6E6U1*GffE)i0N_Z(V+2O5Vc6MQP$}dDUSfSe z3_b3!fI|FZfL?E|I<6BcFmm~VRft>!$WI_PzVLO=u#`KL8z6X6P8m^EpZ ziF$kUVgbzxP|wY;2wYsO)PZh&ovHVL7@F4`nYn$PELKs}u&*@oeEqB~Sq z@wDIJ@?*y8>C_Q%Ke2-1b*I$wP?MmgIGmrKe{EeG>GIl8-rjh*25|!st}OY~h)K}f z+tT~mO#S<}xQ@HEyHBEmq&e{5FI5e>Bps3UMzI<)(oOqOb07276lr4JA1YY!6q z6d90L&(m$p^U@hyC2P6CoSdA>`LX!b{I^bsR5)t%aK^}}iyK81+&1ETFvfgO4^Hid z*KWICfzo_rfd=`K9A&I`qrIkW*mbMPS-TpMAjW}`=|4kg0-1a6K-{5g8p^piX;?c9 zfcPL~{H4Hs!xH1u2ga%ib=>_s<@m_Tnw*_zf3x3W_*90*UcQ!x{Fxi5%t{zS?$V^U zibA~caoFxnu3RFaposN!xsoi#?@#Huk{8wzYkNh9N7lserM>Y&Lm#fzETD6{r24vo zMC4#`Xrqn}iG!h#hB4r+&V@2Vbxp-60(0l{$8|j(8id8|?m0FG_xxSS%eph_Pl6CF z)Qw`n!#kskhI=BuyikjA^{EqPfB7()9V(yeuE|oP ze5#JbT|bp3v0RwD@@t4+Xpln1Luf5CBmLxh4}Ad2fXUKb1#-vPq$nuMf!41PjMy3; z-ihmOH77Ck;_^BkBAciAawNEd*!V~E)L&VihTN-aFr$+5*!+Ph4)&e^=i{CJz9YqC zJg3qCe(jX8PVcD-Eza^R%E!+8m$gs7YO;3(v!ix#rYS;4iBXFzG74&oqJ*#d6j}+YnrR6`di3wVds7HxOd_RBUmHFD2XM?b=WAy zRGld=1RVXbl%oN`gRoy5TYZ-4@OV-u4c zE@zl5uK*NoB>l^^rk$riB%nIbv!l6UX(D}UYU-0$^TzBWb1Nc!feQf+D5N;Vo$tUE z;4UC@K}%9HGW#}d4!D{ukghqpe>+1Y{Bp=S#FBoNR0uLVz#kcmQ)h|LO+w z3@RF$H9%8bT)gLTNz0@Tly3X={+O4Smlzrg;#OHnDKGo|?d=3bu>SLM`>V>G4>T;p z_pQ`7Kntj4h^OB1cnW_+|9C1}seIw5OSA1ZLJKq;fbl@k$lnN> z)s_Ahy|k{&2LCHt$0*;|xWDxz<>yj@e29-dEb~6oG$t4E2A@YVMw>k|IPWylmQZZ* zb&S2fDDG#5Zk37dJ_Iom{9QhJ+@TUj7!XQYj7f-Y7kFBtu+SpN5t)^f#sk?shOYu8 z{-icI6eB%*q58_K|HBx!qvm}C+c?f_^XzOA>`eMhG15Y!r4QZ-p4s{iD`{zDVXjSs z}{yXLRmSh&06XpM`57HCg$8ZShS;}@V7dlLN4->4$Q%rsC9K*@5ixMoer1Y z<=jV1LYocGhw(B&r3-EEgcN0FpzeLR(&^Bs?OH40z}F$uQTgd?UUON)$A%_Mp3Mka za;`~4M=eLx#2;S5Fx;le4%rT*^rR0@t2&7DD2cnCL-@+&Pl(bB-X^vD#z7(?-f6sEeEN$G-Kaf@b{_V3e${7gp{<8@uqa z<0fw-oXQsIWJK-lgaJ~#Tqk%;WUR>SSNZgXd#~Z7^ zuY?kI&jS3aQXAgqb-W1ixLwjZ?bq2fd>@7=o;Wph0xwswqb$D`7j{sIEvUwBxW5;v z=086lvgxO|xR2<^Fm>xPmxHH$O^mI3N2Q;Cz?eFpB@ZCftZcsT-MwI53wbGBAPuNE zXN^D7$6j@%P|Tc^t+$9=OdN3XaJ|2B=W3Gkdw(iwQ^lfi&*(H%lyCmT7@Yd8Y(>03 z7HuHZSljF0*i%Gocm1kO=IT?hkUQ#;vC#hTL^*`R^}((;opJu_WCUlHgWax5SyNo8 zsPwwHiT~3m{1fw{57q=Q$j$}Cavuw}mBhW?mKgLrV%E1S=>!twaQFmG7`~gFv+{o& z=xREw?*yzc{yCqqAbK5so z4PVBcmjg^*V_6^;4>?(RI#P!@RIuEOs6XZ0?~;eID!L{aAI(!U>MtuCvbo0t?u9F1 z)7eiS(ClQ{nv_lso9ZC3PD(`bX^p+ejf%R8}W&8UBBSG!h&f|LB zy79y@?DPj#z!3-mWF4(zqN1V?8OW_t3J8+I!oo&Iq?wZtEl3~M5(oy6y8U`DMmCm~ zxOjL6_pbmcNh3XQwU418$qQl}I@GZ-b`f|;2)4jl_9xDP+51PKakr^o+Lf$Gi12lX@k<#Xu*>7Y-qUioQy zqJ|S!ET+M$O9I++lN{t>imDXMKjA`51w!mB{ReJ^uV(FAa=k%92dcum%5^INFI7q= zp5>k7?5XS#b{T$|WS+HPc;F8BAj~AJ@2>T)Usx<8P7r4DZn!8YAl`^qXi!=w(2b}F z@?abB4-ZZ!TQh4!CIHq;U_by7oBHU$>+Grm+QA!YlwfNhcMSzyJ?Gm)0UvjU5D z08%ZaP+q{zN5GE|87#CL0l_?e@XlZaI7C?OV4Pl1QCJwHWnf@nr!JtApu8GTsvT6I zi9j4d+`fAda6uv?@Gdl=oq32m|8AJqpwQ6y4jdtbn-w9AI2f1!J~%eNa;MC{kYVv_ z8=wU84Cp_Clm>=}g~=80oIJ~rSy&I?e2LLl#Mp^8$&pb12=JTT49r%+$Dbl%)Z#@H z61ukQ1R9!fL~L1d0(Sn>9A^^)f0x=gKPN=4on-dSi-J zYV`)G5g*btQlB-_3)P@Mvo3S#!C7G4Z}~? zCRH6v^6+So43rx}aDhX-p*t88L8)32sNqz1UOaB7zKT3ew3MmdbrSac)Bnv$pvTW3 zUTv~$Sscg;Z-Z-dJmGZXME&X}E?aWlPch-M*jP*t-3G8lA6pm@XpyrnKk<8JoGf4o zjK3ys;%sbC(v2%PNh>k;%o)-{spt>} zq3CvJJ_#bkq|byi=8J`!qU8iyHMPpqcqRwKtoNhqR0J5M1UxtjViDQ(1!m8rbWLy} zX7J$#!J8v^h469e9G=1AzasE`f?XYgI+Xn-^2s)tgi6(DR2iTq|kP33|f$wK<}vi;k79}Fcy439jqxRCk|%V zX5>m+2tHP@f^8v94b$VosLDV#t1Ml~5-=5jG7ZWek=(x*KGz!6m{wqHU6N&XWwN4e z@VFdnQ3n2ti>#*Ra8&$h)S9Z!$6N+8wDb7VcQykbi5vP$+-iRihHv%DqspVR=10GE ze{w)j!*IXDj}fXYh(If)1p5ysh5X```128Xj0cZ!hwkzg%9|2#1MlsF$Tze*T7$Z~ z)gr!;Q=T9xDwL@QL>CjX7uz!Fo;kHMr9=nk%DLjEg{qFZhIs2`jR!lIBClG`%EGzQ zt_k~k{+cxR&L6KL?+xqG0AfBbe;7)LeP)UaPwg;SD>d@SM1+(91F|xkS}sec_X&Y*<_=Hj@;QIM)Ux-rK1F1qO`d>8 z0-LGW(;WHA)uu_2#TR*8V=ZJ0UTM5w{Pk{`ndglF4eAKvfGa$Io9#qEK0$)8a}sc&N{RN62M6V@ zkh((~IcN|D#02I9$?-~5unSI!W@~F32Au#r04t#a1(^)IhdD5EazaUoeA7>?g;ZNy zTpU+bQ$r7B0R<@wHr3;PPj>U&%Nbgk_w(kP81=*_Cnu-BzrT^P7LOO$AI<`k$2VYY z9Z%aM3?d_7(j*Z0;Ru%qm53dw&&I|kZ{#mT+^)B`cU)CN19R(}qoZRS;x9Th7-C2T zBqA+=fpDr5MQSA1I0^~QcH=xc1sD~745&sp$OdBCM)WjzO^81b!9rAd-UR$UaJxiU zjt&lavw0%5Ff!ofhkJV<1~3sJ2S{@4AW0w^{y@Oo`HaMbwWv}-4!k@(e{&WVRA#7< zATto|Ae|v(!A6Ps!bmhP1fW)c;&+GqP4b^MCe?^cL>hXDQOg)3Xx(QArph$0oiU(;RgVq?3H@^F0DKf8Lo({A16 zovF@HMXsD{1M#Bg3VnNnp6D2a%0sFk{gb4;x|fmfIk!o1>&UKs7*ZTRd!t}X4A!s) z9iCNcv51)-=QJF;`%Yeq_s>yCMF93Jva8aY2!<0T>EO23#^(i@K`CQDd-cz*s5j#| zGlnP%zIJb#gQ0A{D3RE(cw{P8yPsAgat`)uhFU*V%`l&GGjmU;96vWTebq#_lfW+0 zN1z|K8+$uUOcc20Z=%p|SkhKYlgv^GR1pZJn&_r>&H2cfr#bPM6u5Sq*}Y)7agQ4@ z))S*s2O!>=Q4&K`EIuOYy<Q@YL|v&0o8BBHop9IGMIOV9I)YN(_T#1lRlZ_9lnyjzr0+^r`_|Z ztJz>Chwy2bM;7ny`JK&DF7^AD#s!u!NdQDk_R=!QTt|q2=a`9Glj8F7_oRlHs=c?q zN!tFFVUDP95ZcwKm8q7bu-ejs^k7Wp{!+^_1g08P5lR6290A^$kJEWu(rD5+d*6>A zXZgj(du{bM=iiRQ`(V;_{rENbI2n8QyIR>4=q2k{Z3L=|Q{IBPLSLVZt%lo85!>%B&m1nrxqlqBE^02Sc!uLsM&j+V^(u#X_hKblEDbnp*b=dW<(9b6;Cn@txCCgtN&yK;k zKzbVUOwrOT@4goZ?$H}9@L!@m%`_PsVSh6ujR~IG;rF9I^?f84LE5CmuX;=vu=Lzk zP=RciU)lkz0XAS?Ty3+PA>tr{9!g3|pk1IZHjecj^gvAbGax*ZFo-qCJAk-`;~1&e z0a~64TmmSL;6aMzW>*mQuihHa%~0=li6~%=AAA`o63ErnRUT2Elim9yhLB7gEI>Q7&VA9K7Dr12(fd*zQxH1qkdCdzyEPt!~m>IO+7JwgcJ{PEfOIe<=@dkSEsD9(Ti#8E&IB7}ls2S_48;t=Ab+mN}P<%`_A(eb_ewgKqU z$)TnY=PElByZGdh#t)aILQwcwcKVCYJ-}Yt812`ws|n}l_wfM){TZx-!mI>8qEPzS zm0udx8{M~^j!w|F9+kyJgO?Mt*=px(8s3Ly*R9vJ;ze3En7)4a=m$>r??<$1YHF5j zwrd`b5%L74HmxVO@ccbxtzTr8cdCielNwiSQ}Bg6XMxXy2I6zsxrEN&XKkBt6^o_0 z1U#0QX*%m0TMM5P!<%0xLyx(_H&fmeSFb~uh~bTq?+<4z1-EsT0>_THmF!;#7pv`` z5%|ox%%y2%51wuxuKaH;gx?3k74nso-JQ0C^DNS4QA6QE>+i_ehl|tMt_4|E<}j--mh3vj>^NVWa2p#VS4k=&tL^$OmvaLs0EG21$ zPa7%l7>DgwFSGpL_pv^ovyeT7O4ht4juZ{H0m>K`yY;@)1Wabx$&0tlse#PnknZEI z2Xj-;Xvf=|FN2#|34*(h_eXA*7ihWC>L4gqJ!{?ik&Cu1UhZc@^HF{N*sMKSr=I(N zb0*c+dp2ASV_NQ;X>|w32V)u=Smy65P?yy7DqFZLeTTnx>|+(A>x9jYjv*po3vE^j z8rEKdlNYzi2U>nQl?Kh`Em09R_NUvs^x*y%F`%>Yc2yVa=E&B`S0 zz1)00r)1UCmO`Jm5aB7Ya~69^>grdBAE+Q?D(lv|k7ukgZC!R^I!?3Z z{N29<%sS-EbR%&S$zvR!lf43g0L`jw_t zL_l|XpN!M0t-IKEdrj}p#$4)xrx=Lyq$C`9UnIf1Kl8tToYb9bB+nS9ZtES$2gk2P zVd8<{zp_?>KZ{kV-ouMw=c}t5b2w%SM;g*UFF<0HYVS5PP%Gn`P;kSE-E|UFC7904 zTRt@%U?Cs!@5KjiL!>l>%&cn+l$Fzaz5RUpp|A4k$(0>{mnv)d1F8#I1TK|6+H0xx zQtUt;A=uYeeq{R+;k8FEq|1}#AxzlHLv=bzMl=Ua^x4kBGT@k-laqHUPaPL-6ZPI_ zR5dpT_Vp`g=i3DTEyMZ6cNPEj^E2uZQ%H#E48k3y%D+1wVl#3&t;8A>GLt|GD4#f9 z;_s`il@&|P{OI&FB{0F72xAQc`FF>nb;Ux>cLGkoRt z<^~{8^SC>J-av+8jKpWax(nsWSE{P2Qt+n7yK?cCK_rza#6$UKdQ zi3&`Wl|^kn5$oJK{S3Fij+%1P_S!ZFxzZ>_Xa;P9P+-6)tu@NIgmxI^{&9F3nFqDt&41AhMKT zG0IUrjdR$Np4Nbi@eFdMJm24ryU!puxp6XLG@5G=lvb4LXNaqvh?$3?qqL&fIcX|= z1}@<~%VVU>Hu+up?oq)5rLCUt^~=ZJpC2xkwwLwraf^-xyG$+tu6emFu0|FX{X2P9 z`R`3nQBf0gn5Ji(w-RllAHZR@Gt`6G>4R0Eya#3o`SH?XQc8b3UQkhlXw$LGN2T1} z7GjgNIJrnvCSf2V@Ao_NTM{;#jML8Ke*aw589u%G!;E$0Y0*J$Lit#lT;~3mjP0-Q zZ|A$OVVsQz4ImdzovwCyrZQ4Ip}N9Rs%itF%$;3<>6I6EWaF_&0^I4p0Q274*>ckrHFbco`>2t)b4)S?fYGFil08?qvhz zrW_UKXCBtLnr$)8soSVBZnvFXV*PP87-v8;AqvgavgRbBA{Tj4L8h>3d@XV)md#Jf zlKS#ZedwItj<2D}vdcDzl7Gp=$%U_llCMJ!cZ8I-HlU>EtcCeS3Gn@VNEE|TPvL%G zY3}P2vJ32eEM6Aye@2+%3G4;B#w>zq9VXKMn+l-_ion^$2~-xFk%6Nb?uP^7o0;dr z1SwCzV;2XplenyiRW53phq+V8$;Y?lunYyojtQpA_42V}E-oT6Lt`f3G=q;a0M)t| zN1P2-4($2?Xsaq(U@B$5Z+m>UdI zxCJN_p{cxtlVpxOCL$vP1CRs66#@C;z@ULXR7h)t)Cp;*UZIg50MV2Rm9}%uQy7U8 z1*U~tL%j%H^G^XAg%(Y7)qqTfG(fuZ*PNjTGJ`SG4NFW+6hZv{%h!+xL07DdCNZpaA2PJ{7A2+K~9PNYWB#Ez!5cg8#= zJDY@y@EMephI}~-^thD`jFIFY<)h*UyQ9Zqzg4-v)88NIJe15kksm<~1@jKW!KUrc z?;g>P$gn9Qrx;smaMjg{W*UQeSTZ@{-9IgxFrQ?lf@fHYQ|Ms`ty^h$k6X+QrhjM1 zGGPL9{#sg?Q*opk*X(!rSgll>{%R2kVecEr=`;E4RtmA7uK!7}dBY$ZG%fs}N? zl=^jjaSRpkfej?LU}TuE9`Aj=RMq0*lfhQ5Q1v36npcxg#~r{cXMGa#F^i1QykN?3 zH>6wuH7I%rjr{AcHFs)$<-W^m6)q%Py-IJ3KANR3$_=i>5_d1Ki%`}jR; z)6XliU^-^cRYh_7nuSOQpYBuL8Dk`{SLTo}-P(_2qeIj5uZ!9j5TUQK-|yd|2DNVb zZ)BNjjMBpvQ1gw1cY|~9w%T{wdzKeuBP~&bb?=a<#&T^5 z>%q6iom1oc08U%3-#As&S}=BdM;-_M$$f)W4`KNHEvW@P8q5NICVAGiCguRFX>)Fi$4*16Qq1K?bd1)s*^6qO#4_Re`mtd464N%4+Pvu z1SP~dRog}tk%);XS>iXjN7T!pio=%m)Z>uQkbrh)sf<~>y!?aMwf6`IZ(4{(wsUp( zConY@Tx*LdX=9+51Nse=q{S(WaNzTIqFs|rhiA)bcExOjc;tmgktFIcAlih z)lY~tn(s%s%G`-n=>+V)??*Mum9MnPMBx84^ms5`d1zjx_3I5{`VuKaA zs>XSbaWoaTsMx@u^ zvd!zs>a9S0z~Bm@wg`8dS_5|uSnB4=kvyVPzf7mcmFw*(PSj!YtF_)+s;h+iZiY__ zQO$$UJXTjeDhkPglTjfx5$CY~58mPlTwW0ev^e4VWs^PaSjUTo1aC`fg0$FbTJ7Na zk&B#j=%`DuTQ$c6IdfsBp^69+%IzBE= zI^q!PGhzUgVpR09=;mg_`%Vhtme%K~cK%VKXbFEzH|=JxktOf385YQyo)Y@%ym z{hG;9D(wxnB>cEUgnYUJRuYQFQdL~R)LvaN;{dBWaSQvm*Cj#3IO2!Wxku2Mq@w)a zUPQKEHQNaO6m%O85|p^}XBeijlEcpE?6xAQZ^0q0+q^xd)3czV+1`ne;-;$7jub<= z0No#bg&~6givLxtoEU(w?dMn&wa`#G;6D~(!t7~hy+wJDZf|V#c00Ol!M9*WE)qJ~_2?m|MC& zJ!LZ!tg;Hj3W>-g-6uM$ib89EC)1K(fK!|P!Ex-Y;aG4yUZlxhFN>3TXfi#lPs6qb zx$VQ_tJ3YqrKO#Up8RQjnXO$u8YNcPI?m5Q#7J!^(TVjF6~T$6&AY}CW*2$;p*lilsxTaawWiM$GAO-)}uG8=eE2Vd)srUO z(s5BL1H~R(Jl13bG*+;AC^F7$ywY;e(bH)^2FPiPRL966weFa_LFUgwJ&I{?s+n=z zS@}|h6Qv_%gw@4nWIJa|&K6o1Rc+pQ(;FY>I2ImujXf-whKXlDsDo0Q5EXdX+7IX#>WZB!cPlau&@MG6+r z$|m63!^^)88qPt+xJzXAq?t-+JD++N%TC6K9dx2*^Ae|?Ago-at9^!8Pa6BmcgOtJ zU3wRVbqoxk*Gd97Qsy5k6)m_qVFkQSlIp+uf*5?ol-BlkqID~B@Xf}b>lzR*kzz)| zlNrOJ{;-55>SKd-r4RtT~6G&^U*H0)P>t|;~@gpPwBG?IqB)>ku;WaSY1gKzry4A zidpyg=k%^@Ll_6vNUyS{rRS&i#8YcfA!UO=ZF*a1KCh8w?tB{o0ZOp3mR+WgN;rZ};uHf+)dPszqVk;$fxa%@gGo|NahcsilgBCZ*%Zd8 zC36&GJWt%4?N^!&cWiL~nTyPykSc$gi#Ib^a%N#h8Dp_X2^tzGgBCKsem8UC7lo;5Grnst2(~XLcRaHqkd)ggi zp*!#Aqoxvy8>Ked4~+>6chTYV{f75v^F+{fypgrY-W#7RWb`N$gjsyc+9OXWZHB+tW;k`(_cX9Jo^2ew$u8MemFnm zbk|m%MdMlNbGy{hcExhe%)Jdp>OY`H&c`XO$O#xZQ)Q$S+L`qbWd# zV?M>iQgBj$4_^-8#7rUO}Si%m^SCPXd`(xw2 z__wo%yy29W!9g_V$%x^VWdi<+(|x+lT)(*KTaSuJrT&xF-a3uNu6?B{q49HT8oA#u zp29oxe{UAeSr-LS@sB*Lm)6A^LpfwEqc zgWSsg)HReJze*ZnyhXO)*MojF77FN=SzO~8p|YZflzc85>=n`HgFDxkxrojA4gH$ngVef>Z9eE453 zss{lu{(LP^|L3dye>LZ8?&TW~dy>J}#>mmh!C2oK9tIG#HL!q(VJBiB`mYEN4?GOL zio2aL5&bWD0}EqACqP=+*}&<)vLvnbO^pE=V&+y(#tuaEBEl*n#)h^=|K!OS+n73; z5wWtfGBE&>>gGm(6eBYO3m_q3Y;J1iM8wGPPl=GZlcR#MgRrf&ovn?rjS~?EAXC`Z z%GN>IPTvr4VG&~&b3$A7u+3m{|>XG2SOKn55f#|UuRI*>3B2r%$J{$CCh z9ALv36cn)83ltm@0uu6{00j*T4Fv@Q1qlfQ4+8@W2XK(k2#D}-2>-gbW4L2#y5``~wIC85k59_uS_fP#TT0Gb2? z3#ib91ZX}eCAu4 z4gt_G7?@btIAr7$lmOj+tZeKYoLs^pqGI9_l2Xbls%q-LG&Bv3j7?0<%q<+9oLyYq z+&uz=fX!XpwAlafFCFDx!C zudJ@^?(H8O9vz>Yp55NvKRiA?zr4Qvg9{i4^uJ;K2igA)7cu}B2;dojLH>ga7{nD2 zK#{?~i5VeK1Qj9m?NNU)`9q-z#pl=cLX$8n-Jlyd%)(%hvh0%G{sZm5ko}(n7Vv)w z*?)ljZ@AWg;6Q-^4-XU>h#%HG@ciw~PB`?|17pL{A$HS(|R5`LRdwCwT-`{)g&axE!3T4r z)G=iqC5nvV+(T7Lc1ED&Q4;@K$V=w^j$A}}7TR5FglqYiK*&H_#VYcSU-|KSf_7LV z>3gbnFn;3LkgR)q9FgF1JhQ2Msn5=`ns2;6zT^uprqu~`EpKfv zrS}oREj3I1YA}T}hbF7dHN}$u3|gHUvv7r`_3iG^Np(uXt;!xSgAa)bHtqATNe^rT z_v{*)YZi*`olirbEO{K|x<2VObjHh51rPR#-X(`8jYt8>xZ{h-G3`a+*&I$J*%V#g zG!|{>w)C_<$y!&J^<1=1jcQ&k-ODXmj;nTeYZ8hoYU}kPVq5xf+)Qq6HD>Vm+dQ2< zw`=&;Tckw8DT&)z@G9Dg!c(u4#+I@Ry0)=Kf3Q9;xVp%kFX$e|D;s07-&$Wep)80vV}hY9@cVyV zpgGoC*ZYEry$O6m2wHm?*G;$|h{Z}FL^P+QYobE?Tr%D$N6E<>8*MeUxllhw9MXH? zA{o|fI+%R{-R%i};1*7fv35AJ7IWZ&SVs<4?bt~L-RjPy+J#$Z45$b#dL>-*pih2& zCHFcqA57QPqc;t!xOUO($!>mK`7A5w0lW0wIm}(d+1lg8m3^_~Dj$u0@bs&>aM=ia zMY23WT;2ad2&wB?$g6)Tzd4fSf<_=zx!GLKsmt}$sC9XZry&$&Ju~R!oI2^xR#y53 zf=m87w)}`?hu?#c)%1$7;opAZy=f+1j~U{8|_y{Rg~1bD!q+=ZeAGR<^lB01}68Ig~q4-@?sYlCKik4!(7jd zTCVg%q&(T|J`;%>l*c3G-AfFMn0_^uXrUv#3XXjpVj$_w}rY_FH@309w2B7QET1f6bB^|Ku&z z&woD$actwZ)e)Biit}Xgk|l=IOi);;OUI;DQaN4UzelBB@@F;yply;nuONa3_4ogi z13;Ort^T|Wjh4jR0=$gPT00nXB%WL~5x8nj#l1ei&5tJz0q7l+a8?U<_f;Ro_n4 zhj85PCR$S5PfK&!^$Yj$+z{pcNn8)9 zx6(qhC7h0~)BKMGhGvoG28KDv>D(&nlW_=K?cK6th`I?Ksa3O`klxpn4+(%(&ZB6# zCyg&ZxE6dNU-nr&dHJyZPI*~Pq=iU@pZ;T$KQqN#?m>-c2l()d?d7mWe4khCTz8T6 z5@PA>Xw#`UQ~7Ijw(;GaNXVcgXKU>=ze}TY(5aO=mws#o?_~J4M)T%k$*v`eM<4ui z>pUpLn`{18Lu<1fX?q~XQ;V`t7RDMdwRE1by77H?SCV66`6x%vk@?p3mTQ8#b#u|W zHLq*k>;!pI69P^MJpX3rB(aZT?KKhsFy-R-C?z&|!ox;;E@6Uyw4d$-u^c1TPF9mk6 z+SFZ*97D(k<`O(K-q<(WPgM(@!gj{mrhkO(E?WI>VW!uF#BIrx>y;_~>jo(Eu9P(nj&sTSY zIfx_Qe@Uo5{~_zWi;sBeL#1P@;gYM|oj6WoZFHqGkOxT&#OQZYM1!*U&=UCJZMcus z*{V_HOlh6h+Bt6nQ*{`cjTMif0=BagA15(Z3%0Wyxb8*1%x|LOE0wd}U+4A>Bo?dQ z-V>=1g1>gRrF)2d1uKn5YMjJ60;h^;n^eez;JOq0b&0<*pTn(lYEotiDmh8pca>u%CJE4}MHv3=t=;j`RPUO!f)6z3F2KPHrR2|Jx zIM+G|Vme|vh?gR523|1cH@4#&9XzOVDV&g*l2&d+&WJ!@|)>+7R_f7afMGq|j=-1Ie$b?3r>BHE{GA=+}Z z(m9Ujfjc2i04>hUto_2X&0|%&e-KX=GGcsyY??6sGJ5i`y(pTRax08q+vD&&BEpKg z-cSNh7pfj*^pbQ7`u2D__^wv{YZIS~6mwxIOe>%of-Wp+SLuwO5Y_*0 ztXU$s`;8WwA1Hp5ioAp<*Q*jt=?Do_&37GDJ5=A_6W9d~i*jhR=5uI#sgS0wk*Dvw z8kl+vIxPVhe2*><{6r#}J(z8laMRq{DA)Jv+r^qo^2OfG_ns)Y-w%qr5L_zm3!3{d z=l2>wHC1#!cSt8xwO2Y<&RsT>6 z5wV*DJk&e7;y|F5i^}xa+oN<`zO4)(p-3pA_Nw&hPl{N&{)_5ctU3M3C*XW`{-LV- znQqE{U>bC*UA|_rPz77L<{_)T@aCW%fGA(+R)DXA{{Jr7nSPq|F^Dd>MS`tzlh<^c z-uhyKs9a=$bWrV(oD?)bUbXX-WZTdgBJJX8l!SQZE7iR;B+oIZse77Y^n+5^P5@V^ zf4r?J7@;^D+@_{iQ;PR#w(U3uJ(wR4Wls3=K?%&Bt@5p?KZ|55z$}P1zur5xTJ_K_ zb-HnskiXTx#(NB6=3)$CL<0>8n$D3IX#rV!iJy6LfB0u{DPwG_ZMRKfxM|A0 z4Ps^+xXO~w+yARkgPQec3X!om`FDDzjp%&v}s>k1lyaT{w(Esk@-(LZ|s}L|@18&tZQ@sThiaXauRkyCh->H@F-j7#uC2Vo#?a)G`DDY}_Z=Q-j(Or~p|8?+K zR!jz$A3wc3(2iDI68MJp1B+;BO^V8cPf0YyF?OF{6V`uz-;^$MgDvyZBhU=nkw{CQ zU~ckr$%r=Dpoa zug&Kd!t9o95`V~=`1tuK8>_ZPdiAZhCO-F<_?D8b$A5G-&ajitT{ZdFRF98k8$b4V zw~9(wb)ee!w~H~2N)Gw3us~CNdx);DgX2%P`nRf?US!GGDp+w6CHvRWxk+-jO|o+Em++mJV^)13w#YvA&1%u)g=-VZEJGnGC|K954`-(1;DKTWEh|2w~e zoqSxLlp3JF;Ea--M`GG1^yIK-62N5hT67!~pP0Di{EhySHb%x0!DO9m@`f-xY3d@C zvsfzG)6Z)Hb5{=}b9sR+>}1{y=thY}xa@&p+KpzJa~4K-6T86usxpRvtfTI2)!s8b zz9;9r!R(DPwD=iJ^!71`7hz&EtPQ~~nX$Tgyvn}b!@Yh}=>AOpX)7L@Sy7QK8;P(1+F0D-TEHGwUmpbyf%N z{}>1@HRGDKEZqN;la_pRIV+_WSRZ1V!==BU{J(z5iU22&EozE~?Z}H84P*VFOZ|Dc z^%pfVZG0tj&Wi?5E`>4k^UFxJ-Tq=x6yIWB9!| z@=f;{3~je!g4#ZQX0YSUbp?LQ5Y;C735sQFgH%VO&VZXLjD5+{bL#U>@s;&-+aKXe-1`vdOhL6U6fpO3aT}(kLq67S3*{`g z!ma8%qZhsiyo`Kk{oyAmA+dq53#VzRH$y-f%a$o2*<^Vueb0P5m~CqQp3JFeu4_kg zpFy`?tDozCXG{3??D>{C2ZHK>{-9X zAM8Ww6?=(ex$T>Q4MRvCd$H@UOuypNxsLml$D4 z*eEnq`R|;rtVV7v7*#44yyfcDuE`gAdp2_!wxZ-g3n6 z4LOH^Kd4m`c&mnUcsVNgCYkMY3+R!-RmUjZ4C&{7=S~haiqHkA%wc4-eae=u2fN-g zPlbj8dzZ2?mjjy)A=s7P`nlSDq4P!poeq+0Kc#|o4X9rLnkXWtNpw{>1c$X>9Q~vH z^A|twTCZqpmFL>lZFF~SWT(=zsFV46f0?_xOJpW?9HndH+_Vb2^Ok>w0XcTMlI}bVH7=9CS7*@!*A++#iM2OZ6|kHMED$I}YOJg&Pp3;TV2Z_c<3C1khSi6t{v91K>>y)VY_~Vy4P@( z$v#1x@O}4W;VbNKL|E^+ixWjf!yAV0-&??ae&|}Cex5QNzLY3JeusoT(B^! zuiD*&v9v^$xXngk*PltVh8R3|$u#Sl_p0ovEy>LegxGOve3*J^A#jJoX{|oz>wYXT zE9qKm-FfuY47nmfub9!_O<5Dvj!0DHhTC0YdlUjyR^(l?b7hhvF=rslrTme}K1sg0 z`)Ok+9NIkxc2GEjv5MT!HNBE*Eb9^^{B~)*Sk=aT4Q?2eY7yxXgvZ+L5pt7;VW&|q zQz2m(hasPOCuVbyxi$9Nf)~A`7v%yQo9ZCp-&aOG zZ?o_SC2Ff{?hu0G_tgASVp!*t6F{kUzv^0Q%$3%KgiN<4keYEFX2U;*FONKPZcui$ zc>l%qoZWb2YF;6dR;0PcD>iA{1Z@f~r+ zBL2!VKp1vL2(a$Hh}XGm$~)Fb7hR1U$_+f(IHhXC*wYu`fki5rT;S&}K>f^;#+F3ZOJ@mDA zGaN0<>@Z!Z3i_1O6TO%`g}m_ulh>SQ^c$J~SoXlcD%f*@r=gp{qP{E65*TDXTW8I$S6k77LFBkaC&A;V*Gfy4tTdV0?@lE>z z4X?$E$$Tj=d!mr)=h~I)Ui<2c+dMWvy}02+xHPs@`*og&9BEiy0#_Zb@jdyhm;U7t zOS5a{!A++;gGX~R-a+DEH0gz3T8d-AyUVoP8Dj-y&e-p?4v46vu}|YwL%m}ajlSof`nD(E#%hqU*OM|8%qJGv~~>Ywr*592K~J^bPT#LUpSBc zcu-jtOf_jyC&ad>rX?1_wU?AnMa^2re!Z>%I>%`+1Dr$Ju{{d4@wvuV&|^Uo-12e>z1QHGk8Mu1m$it1`k#kmP0a zFaH+gp(CG=R{&H(#GI@+g0zBJbtJ}!Z2~{?I|4{Y4;?`&D}o8CRRM90j-CD5ZWo?f zt|cJvqngGA|8yK`(Zwr0q}F&5(d*`t=&$^4@7ywP$R^o-rJ#{0Q&7rrvT4DwjexUNP_xfj3GJ`abdfbIdHS47sY7&k)^uc=&E* z?#i3@=q4fqjh`8V?dw$m?6ZRyfG;xzaK#i%4(+|oqi!2C@yp}w5k}~uDxc*)3|%mk z!JbU!8pCv`qfRq8r}UnYd#t$Y^~o0_0W6d_l!0|Ni_OY_kY<|>&_5PMBh6F%_ zg}^FYZPIh|R0`oD*N^7fhSxrwS^!neU;K3Tp6v28KVXLvSVc#uw$PP)xF3)R*GJ!+ zyTebu-4So|j#7^#eE(qyQ(o}+o#Q!QIC0xy$L&Lb*=y*%2n z>CbDb0y~t@>zzehr+ifp3CEyQ9)O1u7x5I+sO$A$at~DTEZt^sEppT5*Zp$B4;cPt z71^+C2Vo$=m29Li1DtF#B4!`Zg!pz^v1lNvB6lR)Dap5Ts25UwI3F$}va;x1ceTX* zO8C^&q_DMPLbJsEOu64CAdr&5?8!16xs-xsVsCoz3xXiQV$@=(Dbe3T1x&d@#`B|Q z3Lq4(_9DiW;d=H%%?aq(((c1uLb_@qe>#-F%(2Td!DrPW-uL?Ve)p*pc6hBup}>Ot^TnBZ#0}l`O~z@g zS?H+e2>y`H*L5nmoD# z_7~MEU`nKA9JZ%(ejc`^!CM7Qd|nN3vNg#%89B9k&ZVVBTZ7L_mExQGQob<@52xAU zc!jH$a_3VCqf@y%{HAs`9RY3<%K8`n{LU5?)s@XZe=+(aH?h8MSN;e~mpb%j^c@Jc zqR#Xv?z;ahzOWk~yBno8cO!A?5$lORjI-HRuYVDM9J0v}Ka*zFC*5&} zXbkc)a5L<5xg}cXqCGqj2ji|+cJqgP18NYKRdzZTz9rmNttcxnf&<)#9b{(#f1QKKoa7N~)x-+%fJG3K=zQb98bt{@mk~p4 z!dy{en;lPnj`|cv(45<{HnZLtuAcgQS@z6@S+9k&aOa1CxK~F^A}#)N4;p>0|Bbr% ztwzLs*K(v-aUAvdKp`W%mx_wm#)A;hM+DX0JN@?uwp0D;8XD>no_`qRGwWMlYt943 znPfjjzKnP894x}~(Z=slRO2S$ZyS>`iKRrz##$q9sc0q*rfT{MrG>%f2)vL2t%VE= ztK?#3^lurE6j^`uGSSkZo=3$6vmgG*evr?&89j9M{Hu1a;F*n&__AYA#|BLr7w%E% z!9nxuK=JOdOh~Sm`e-MyMEkSLPqv&o`*zK_YXy2QqH=8SFXu_D48*Eq#N1&4-?t{Y zhx>;zM{*y1r!!Gc9=UmgFCcWGwSL#-EQEU-et+tC!WR8d|AJ3RJ3bCZ3}CQMQtNgV zt3tNO`YjCsEo!_5bNYG6_2rJ={j9rRg~(?EPR_)z3bh63(|PL{>}V=_SBPqXan6IM zB%vF{n61|7kRrQ=7HCVl@wYysTQkmVtdVhEA7g%bzimsqf#&+Qc=)QeHh(UsF-i@) z_ZBmhz~9X1ovv{HcnpFoeyHVY8SedSTM^$g>j}p5)q@exe`PG(x2q7YuZ$k8hh9hTz)O^C|`c@LM~hv{M#fPL$UH8`pupx zA*5j1`@t@&{q_1kmgO^=(_Wvn-q&Ti*|hd8HpMezY$%Z;<`DiN-)wk@0iRxn2CmZ4 z%gx~oOC1(e(@Tg;|2xK|XhzE{bEN9xNQ;p(8QMO&w$3N^C?an4`}XfwMOxgWcyH89 z%rx+goWK+}rt=5nOj(a39LOG_PpvEMgf-Z2`>|d^5?rD4*F7#-8^{Yep8DzNWR^3V z-=9Wt*HXw`n{#O?w7dD3zO$t>m=V5(Ct`NfDd<%nilRO3(`>-3!}rB&8?WD~?0Mn1 zo7m#iyI1ezbzb-vb3$Bs&Sfagpul@%+}FUzIm1#4MP2xHV3D({Zm&eiZdmo0YpAbn zs5Oi_8qD6pxH6nkGe5u-Yg8l*M+o0xS>IXHZPsCmy9b4^yQq6`6tu)#E;ed09ulr+B;GId@& zuxtTSytwEY&Kx~FhY=LCcNX*fe>n9COr4HiDIb6=LZ$T^{UEAj@;c+@KooH z4f+0r$Ztvl#Fhjn%!^rxXC}edlYJdS3&B&57ovz3@F%sSQ@EG(x7cNM28+QlNIIFO zdJKBHfGKmRLfQ@h~`8dCcmtNog-Rzaa|1TkqEcUTUjxs0lQSu z9#hTM*-`BDhqM5)QAB2#!ysMX8L_#A{-S?LD*eU=tj}^4SRYPZIQoHigD?>hnfiaW zQLG4Ovq+<7`Lqn`YTOamC35(Uf8W|~pMG)u<5RDq5u@D+U>=Ln1CKri@~Vex8)Pmr zBq-U*C%3F9ljBp+zYFi248)^fJcv>Mx_k1L9IFuCS+pcbX@;IZa)2dRRuD&_yOn#J z4gJf);S3;}&!oX9cIZg(&;Bqu?=={}NL>!pI-BqLeRg*s+iZ52We*J?m{NRJS?LHjvcU0>U% zpFOeNc@RF2ddanak)d`B`gwYf5#I&V1Sa(PW6(cB<;J0~J7hbquU`A!>m{czUhB}q z``wm}6IFwwqkHRqkDIohIe*4C590E7+W1WENzMOZ?gP1iMGPExr~6A|&^Ny-NExK9BlkDta2Yd8i`ymCJ(Xd3}$+9<961nmLTA`z>qtdyD)!t905 zFeWJ^jh!|mi{-X{hz$5IE}FiHFTrG^e+Az6Vi5MK{zWyY9=QWwiVO>6`OgcRSNX*) z5n^p`i??WVN<`ni@#3i$i_Z7(@C5U^)24~qAgw>l+r@{k>3-G03|5U<4^d+G&k6X{ zNnX9nap}-$Xx6hN@r%JX#NWM%PM{B4ZofZ#6DU+4!C)bf`_^6WMlpL#&YH`4~KZ1sU+9R|H#hFeA^kj)F~AA*S}saZeNAD}pNbc;Ux z-!&ozkiE=A=|HM!z%QpdXu5fm%Jo$T4^qsMR?7O8;VPnUPrbjj%am();M-^k_@or# z&O#G}Vubye=x5j?H>&QpQsc{5pJ&X^-8cq$Jz1z;h$J`WVgG&G*K*lSD@coV5cfc)6kbNj%>)N*5DC9MS`om%Q^F_M?N&04u z??WH$?q(R6AM&4hr}HrbuUa1fnDhyYnSPb_+2`9n=4DNJu*U<3SLknBOOc#4WdE3b>w^V7?FDcvL@cy~f}@L;A8NZ6<>%AX$@B5zyjxLf&18O% zXB_TX+1sa=#2BGUoEFF&Qy4oP7=mCl{)I=FS;yIF?hS4AH0J}E z+K-dQh8=^*;wFFl84+NH&D(`z(4~R@@!!!e1f0jeSn?ifAqUdgkBX4zTy$>I0!nnr z5Z09sCZ*H*XMg&KXb4N4(RdXF!qgyxMt=C4i~F&y}Erh((BB}!g%;lD#HboU) z2xHt$Vcb9o=N_gMb0h$vSM2mNbTj|QCogpR{^wC!QB>~SRp(=n9uVH;Z@xJO#Vj6! z!lED!kOMi?zRL=R9;&azuDsSQONRWYDawv2R8gMuU^_bWK z(MogMWXzAj&U~oBAcFZx6iACn7={vu$Ov%p;ORMJeq27ucT3PiiA=mfJLOp?^;6}| zCD)+qdVG&T|EB^oEC#8A@Gt|GzPpQW)gs7I%*NG}hL1QnZ*h9Igu2%(#I-D;HQo9h zN8CMiCRs0LsYRK6{dOG?Wx5S>ZUrYxWil)KQ*RQWy3_x0>c*;Kpot3G&sX6ys4N55 zUY>?#OS(7m6{s&S$~Dk4O~WY7J2iNML1y1*={`W zd%2-4MZyXpu(S3F5On>`EUR7;^r}K8Rp1j!#kaPgl&z|)by>utH^u5po9vy zrG?r`Ttp)mqtBNiG`yiw2opeb`+KNH?Op5MrO4Nt8q0jPHq%vE;lXK4B78#vEu$7~ zHo_~6-~mCmeGZ|hvaUDTM1vf=^_r$|wVxkZK1LQ_*y7h`%enuAv1BbfhVn4xsKAE{ zTU7ceLC2=8Y@on-FHfNpO^Asg6CxQCozc2;k&W`Q9tsKF?o~5lk0@3*NFz9WWxZqw@qw`demQcb9tX(X5aRWU)t%<|ze^+Nsh`BGV!!ls zx}*!#56i2vS-$yQkoH((OYutN4$a(Rz6* zbNkirM4IpDxKVXolHP#IqJmg!8@}&c%fdac3>N{tOI;H~Q%u!*oZ7B|Rj-W_3@7rZ z_S}kxedh*KAO%v8g7&2*GTgM;e&a$Ivtz zl;YMjDx2qbS{Wks(^84zsH!G9;wI-+UmaM`3Z)E$XSO>gp3TQOydR4nUbqrEl`!U<7^izkE@~JAn z*uWnS&zVS5nl=q_s*X{um{%`XK2b4gUAsPwj7y`G(nJ{QPJD>%q*Ont3L zKZEu!a@qY^)}9g=owKQO{(M!;{iLdWVks{DYoGEIJY8+HUVX-dix%4aK?K6?Du{Bx zp&wL6_ZZhCY7O~21!kX=CL-(uG~TyMnRC8Ap@E%gP$$D$6DX)~xe~=oLxBc0hn?`82#0lC&P1W7p35`d&50*=m^(uSdq8?>@?LK8(EfT_d zxJpiGd)(qA@1>#OH29^m4A&wkyS(q#J!I*Ta$P?-I6hmZ+NZc;Bs+U&a-4u1R}0e3 z0?h2<;)^?J&ob^n;S~Jx%f0&+=oR#cXSdOPfpg@{@@T^Se)g91s2F915^riD>HR3NTp3a5*0&`mjhMN9l{|IuM*)UV=GS0E> z+QLZ?3Q(=K3{e1qfs0Wj6TxGUxa9|A4}=28w&J6w&up6rA~HKtnBV=Qp8jU@sU+^e z8%OY^bpD8618c)Ce7Y}r;(1L|%gIatosqfG+^B)q11QQ@Kv5P42Ty|8!?T+hMEijf z_=>X@teY4_6m`l4;Iq#Xx6OJvjW^%cIQC;|TRxiu{f`oazQuqqI{E$15xX`zA1V%NL;Ws1PWEt%@ zO#peSJnDw(|NJ9=BR2w|!7gVrRQHv)h4EjoG?~5N`X{-lCL^S>KGsDzyLh*F*3Qsa z<4TneyvMS}qpZR5CF|jioBF*E@EFC1 z7g`R5P)!hR-Yh#8crFdwT2)Bq;#P@$RShqE-|+m4n655sxM;(PsJeJeQDHQ#=I}Ox zYewJ;Zc2li`zcVw`J$xcXr+~$g)Pdj*Ks1?>5F)nav;Eud|)uTjzJt^FSBUbXDWTp zNdY42!to8|NP1b3z2*1ABNGB|QCgqy*FawfdqmjdFB7TmT@4I7uKr^XO-pJQF-TXY zJ|>}B*K~+Ce7N{+>=tY5GTgR^LtO#>BIRmbS$9mdp5N|d0w+zEt9PV1?81(|2ZE{W zx8v^bn6NDWbiH0sHGR`GG?^V$F5x&3>f@zF7zYw#dk9Sy{~kY+bq&GQGR?U?4fsMM zS9M)oeZ$C1^z_=0VW|Yt+;!czpl}Z3B1x@qklISG&Cl&|LXuj}8xC||B~eR9dsPnY z#vn;ED5t3lgyO&_q(f1&_S>q7*&d=J^})2+Slz4i+x+`>fPZQO6YkCik|R)h5P61X zvtR6k2@@zrku{z_FVoFtzMch&#=MM`;8>0+OP9b7DE=*Tu04v@;WZGi2l^oWS};FspcS4TOe*?!AI0HFx~T{bNd7yQHI^fn1b6qP3eFT@MAwszmbIZTh+; zT}oa6eGuH@YyGFYId_hrfdVvn5^8Lg6pfylKC?J#!xic+>r+)xUKx2gPt>}uytGI8 zTlsZ~I0IE05I%rzh=&Brqk6haf$ilk&rI5t+0|d@9S`}rj3lu`UB!W3)U7WTiB+0` zcYn*6cj&XDd|9>p8}U9#(-X4$z{C%ZKL$NFR@}8eDhI?~f(U0Lv=ODhO;&K&=KN*u z@s|8nlKTi~<_y=VEELjthK&(X$P6TW>~vKsRNoHLCMNoaZj{?`F~O}8%7VRQDSBQa z{N6p28~v)^B)iuGr#Tnmx~V8DqZE0Sku{%V(1~TF1z@18gT#Er)h()5-^+PMTX3sI zyiMorO(JP6>>KC!2gydy{@*ogFjqi|4A2XyI$*%T6BvDu5$>D!z54}UI&K7BFRD#E zJlzFS@rZt)^vT?_?^?-;7p5>>8l*{6y=9W^ree#1PV$eXpQwV0p*6EnmSfQ5-4pt^ z|5z0BBsKj0+_B&zGw=2L=SrNVu}@_jOlxPSlnb`?xhZzgKtX|j9GrxS1ddqk{zx6E?3R(0#@!G{yfr6=o|c7c|q_+A>2 z4m;!k;5U27atK3l=L6_KiUs;a;*;ko$fEB1hq;G3p0PCEeS&Qx@7@D)u9~S$=E_4@ zr1g=!CE0^6$_&KP^Yy8%qmXjkCu|n3v^-qYvR&!!obm7IQ;Ai^=oxca<=~h$($df1 zLq}Mr;M#j??aFTaQ9k1mVsqIkt^Nsi3f5{uFMpryA<0~Jms`Iqx&h91y{=~8BXr;P z`i-UMGO`6OsthUIq|?zWKN#hK;@{{M1{?F?3mS|>?(&3ZK7g4LN@l<}(=nW9i?}W- z$HiW~_--=?(TjSFXJ&BGoM!}EjKNI|u7TBUE{;V3@@RYeh;IiQ=R%z_51>?ba*{KU zNTKrsUiJe2(&Q6R8P8o~qAHuyKF@M-($wa+T4KhRl5Bb>d|&r{nEhQHmU^W@lU2lM6EF+JYNTK%5EY4w5x%ufcPWtYwe%-B4(*Ia5nNQnm({vBb_&e#E(q8H#nh=#%;bc&f8~Z_DSb8q`>pRf0 zvEOtRfu8Qs)_*9fiaAn9X)FZB9^NRhgy#Y3;6#r3?6YrrLYt8>Wj)&A$Sr28tzQ;QZ{A|ofR*;l7^81msQ!e%$4rpG3JD!9 zz`HAI*nBX*KK`2l)|qk4H1H7%3i%7ybXXLlQnR_ z;Z@`m_-vpf+()vwxTMCzu>eG~ex-2XwpWoRc=CR$^SkccpQ=D02O z*x~*>75&kB?fD|sGT}vaS=Vey&y5Zw<{f?6Lb~Zof}_SM-;&P5f#D+plZ{3qRA|`7 zJi2hinvuKv8TJuR@qw)c_>iZgr+8?Jxf5lJQ`}|70Txlv&)9V)IT^s)p!9Ck(o(}| zIjz-V0ob9sxxdU-h>Kg88-om}Jbk1bqpKcoC@mWiKU3q*exZMY&pq5=LXqL}0C zjH9x?)r-V1z(sLQs@@ zn9~dZxq!4asOn_FknL&izz0;Hthd><%f2g~;86K&J4y$iJB#&iM;il{iR|#Rr=S0) zB2&88oX5~ajzZ4T1wFyZa59!}DdkD?nuNcyo&JT7vp?@W4|r;Elpb7& zZ2z!1_F%N;lzWKj=Sx6kEb0r|zLMSxyt)l)CT$N-G2S3HA2wg_^~pn+SbcPD6dt|; zu21hBOX`k!$JfhSDDEKg&ayd_4N~Ar&j+0SXCoJp7P_lP3z&V+QrgWVnh_+tzqHujB+SS}&3wdce#QjDb~)>g zL4$`W4BNs30M~rrL3Lk2a-jZdZXAP**5QWJth-`BAna_!&q)6x9+Eq;29Xx8(<|U;W z>D2&5-|VTh$E|fg96UseYm?6^UpSZHY#z#dYQK6mkC7)`pHM+U$)p8=azvu37X0;RK>^hyjHGx z^6<5C%F#{0j1TcH&?Dtp=2nl7ewIpIS*{0Ww2XGaH*@0?O%fJcuSA&)E)4K4%M%9i z*YK|RsNzEd#t`_N&c&dFBFlE<#fDMws^{!beeI3Q_wLUmSUi!c5!!$O)NxbU^yZgh zDMnkb2|!R6`+c{jzMnuim27{tXBaf_`Vwz_Nfxggus{%_ylNF96=3g z2TCpPi52_`-o~!P;id62^MMvGqOpKBF z6wAj$<2*Anhc}^&f4(|X*mL5u?T*+X3&q&q}IEz?w;64$ks z7B_O8`rE^$Gw?wW_Wx=mJAgA-1rM)GA_*v*ez3Q1r9T})I?N*MjH%75R8;w(C>86n;Tt!-)yDz?Kj?u$~uP-zDF5V zX&rQ=4X*#x>^mJqvPksD*uUY70$CD_CJ&+W6f#6`q&a*tdQ z31_v)j!&^x<#$^@^^}#K2$uTS2&PJEmkTdF7tJ3j^C*X^&lQ@C9&rsSO)5nCUu(**5;E6ttOXh6EU*4eO!Flbye%bUw1D4;(v;` z7#W@o8|ca@K`+dw_ySEUk1m#s?|RaBi;+NIn6}~!m`fjT0RSw~FAvPi#`EV5zH<8N zpZ6Cz0vb@4-r9}UqbILE43BX?Rip9*$NKH~iVgQ&M@>*-538CGC6#A58G z1AL+QI$v}KDIHvxGAnMN=n0n^^W6zh#J>odrp*4b@o2_|)^dfYAdvl8G(VHz2W4Sg zJFi=W+k?@1rciHFSdUVCyjS8X<7=e3S)}i6n8tqs8Ga_m{siDB4s>1^AEF^^xH8Q& zor9@mw5-4${y6XSJE_Yz;=Ph3FMd4x{fyIHk4)M9JHW?@$iFE`Yso-DAwYXy*y|%f zT$}3_srh*wMof2R7epk2xtS z?UOuu)Kxm8>Z0uaPvDuqLOifESM0b;BY4rT(JvUuwO{*abNc$^a*_VH7xna49& zm)*|xLE1LK^0CpIH(qOUW!-cC68-|1c+)@w=mJ$u)BlqfGoYs_okD(c*TT<>|H(Zg z-ajX-zo>=(`G4YQrrCgMgvRy}RKGT{efK|cw1TQ(ApUF&=(D`3=lq9xHJ2{zbBIt6 z(Gf>rc98@uXo!9<4iVaN5-8SN`twNF;!F;f*owZNJ+bRzVu_vXW+*UD(VYnY!L1BI z>U2m6M@a)o{vn51*QRgs<=5j-QPX!aEk7(S?qF91ccDia6<{J^7ePU_qR)2CzihlV z^nCcz9a73zKhLj!o{d+T+W*dask%J0u}TK_FVIEd$Dpe>hX9M+AHr{EY~oem+jR`i z%45)%^d$gqNw`cOnEJy~RFx(G9e~WO{}v+1ZksSTfW_-e@e*WL-W78o58o=o-vv-6 z0k&^A^&gsJ6hKlLqu>0P4(tF0!JBLU$K}!!L#Stm8|+<~lP~dn^=^*9X-wUT*f;!X zYYnBT9;tI}5-{oK4eZyMB6s@nmh;nV?+@=GGI!q%DXOxl9D^<(EKLU2J3<8?R@S}< ze(d6|VDWtB#JpOkcw}2}=$pPXBD08|Yrkd|+e>6pzl}_oc|{B*;Ce-iU+k>F#rjM_ zu{Q2}Ss4{|E;rW`(MWv$p8h+!a$z7Jje|cds{TpIl3Kj2=A9mQVsVdh_qb7Dp55P_ z8D3kDR)}yvwQhzG>VxKL#tDxmdFCR}rj&nv(mto6kS-UAe`Qr(85sX^x(|$+3x)8Y zj*4I1%CcHc)Wm&USLG0rcdU%2HcsZ`qo2Q5U}Amp+V32T`*-f}{RPr|rsJU)-8+O} zUTZ9CIHKUO>Z4Jq#WEAFbtBFwRjak$YS?hIaVJ)&f5XY~u3drj?^2!i1U7eSOP`E9 zt_WHdu@~oO=U;1Az1S9-+pp2{HrDN6BdB5<09n6R#LidKu$1y&4DcUh7vyuj08YS$ z827zWCQp}Z@$==w{0;_@Z%UY2tpscnw&kVBjita+%rUm)d%)G+&n_YN!EJrHY3C?RkJRTrK8g28g&fIe&*bh2KYRj~vy?6KG@6We-9*>MuMU0ibLl!X z%~|fcQ(&ms;Ac{>y5q4BVEyv#-E_r>*|ta>Ff|b)g_d)~4f|j@+3T3HmVFO$zG>GK zm3+VB;3S418h+8Z6dQJ)Dzb6jW{EPnJn$E~l2R1ge|AZ$VHE9oVY4)Se(HBrn1V!@ zp!t%7-}Mt0@XqojjdJMLgNqom>F=~!=}V<4a~qIS6+DZ?o9x!oyKzMxIzL`hm9<^_ z=iMh8%oA+cgLGRMH~y#q^vVt#&C#53ehR3qXqLpCJIcGSkYzQH&CUOq#hOo>JF#yu z3k$y1p}Fg7ltNT53~(p90h}*R!F<@;GokL<_RX|y-^Lz~UU|#xwV)KRC$KMTy{dEW zlG@LugI7=G)3UQGTrc|nJByczyx&J{y?rsHx~$T%*-lyn&ruJ7yjP8Ib6sh!{pf6z zK7aLvvfeI#y1=IV+K*02_u1j+RiD|fn#Hs)h`so@K&ykHn>~tjB|Ickp`G%;e&ARY zY}sVLn_N6J?^dr!Wi9>PM;?y6OR?R$(J3PxVN%sYdw&<-zOsm`3NtemO{-82%D5A@ zaH1pm!rJ^#dASsyLa+Y#mbFU{%e>rrZN}ZxQ&f&7g`qbSKQG~Bzo-mAx(=XrC_9sU zywiC()XQVgNmmQxHFh$4G1%Nq|G_Z5J!D>b-HY*xN1XN28OLhY$`Em@j@!11ZRk_9 zB$DD9gjDA=svr*`UEO-+4L-jLc7+y+LzfQ19tkWfJ&6%I;cI;^ghM30W11FkLBX^@ z3nQX*A=sDHCak4&U4X3hth8ivHEO;3=}8r z#&Ha}?w(t2Zl0g=f{b3p)s!b&Z5Gd~YrPjM2NI%gVh-jFRlOr!DDJntyH4Kra3 zvwYsW@B99a_dVX@_s{PS`KKAr^W67yU*~mR=XG95br{DozcBrz(9AW9Cc$M{S#F(0^y2@c zS;>si(3ekWRrB*vK5@YU#t1w@?LBF|XRNHZ0U{;Kd;f#N>|C zqt^U~bDO0HW@ONWjefEM&6Fp~Es%?HR^%zKJ)sBT)9oI_>q|pOUW6U4GR5zEWm&nE z<@dfYeTh5pAK6EsZYAqfB2fi^?E~Vt^0#V5cj_(SQ-AxVVT ziz9rLuzvKk#M&k%r_fs5Y}TirXGAl74iX6&owBhSaw)>pB!L?2P8Nr5{^PTzJT%O2 zw=P#+JT8>}>qBK8#jRCL?o{C#%zeLOlVaCuV{n=l>$NGfz9SOnNh^tDhr>jS7;+IC zdaHP68<#pc>}c7l7jPvSdD4~fd#<5-5tj|5LQ zbcN+Cv$vc6M+4V8gzD}AnE)1;du3dE63LjVFM9Uz14WCp4jP}dQ49lsK&1;fZ=eG9 zT%LQ>0a*BTfQ!!`MefX16yv=m{FrPKd8T(E8@NB$5JB-YPaHPF4gs=_4pVo7(jpf# z+ZAjj0_xiE>2v5P#HxLH@~ndEWYS(IN7eg9``ZgN|IsL=D?iCAKW?X8`lczt-oIS% z-PxtMKBL9Gk^qB>PJ;%%Dd!b#GDpoyWM9^j^-cG_d9fyu71QxU>4sEwWqV|(pWhXA ztU7%?J(UnD#0hHP$&%DRENiZX~F$d=f2QJN!fD zkHbZDBbhOanL;cR2QJtJQ+(YVwYUOAiuFA8rixU>;8y|`-ms z%V=1;<`}!XqqC+xi`!%j`FhXhbKzE}%cOI0+$~w@{T%%zgCl`k;+|cO*ie9oyHK0?I;$VXAUU!35 z*}?l;8s zm?=7}DRHvdN+4S$65b~cDF&~u=q*EUf}6j{<7E1IC`b|x;Ikw$MUBkwL(-c1XRsHZ zwK46V|Dv9NWK)hqGt&9|rJpNke2HuwosqdcZ#N}@DIBCKkkyy@_s$c;`^`pocV$#$ z<(Ac34BiP8){p6Sj_N%9>iFdR%{S;xX)3)axT-3g9(Hq)NxC~sdfa!9k^H3Q)zkK| zN=whBNihNbI1wZM7yQ|m_VXoaJ~P_7U}6OAEZfOXj2x{T(e z)FKx@Kp2oulA?Wx5zCMmSr4bbQ#-yP0idJ9vj0x_!lfKZnikIlZ?zkV?kMxB;{?W= z_r^J;6ui@>WG%6e%@-l$i~WQjL~!3{Wx?GZ1xoi%?%P?pHha4npUXP4>**_dNv8NA zv(i31GYYmW$jpVB~GKozyDDW%`!Zwj#Q)gg*gD~9C z9p4G$L9^RQ0F|j7XiYYRDTMw1{);=6sk`wmX0bx(w7K6S2~?TX=@&LY zmOT98rqLmBHkS zl#w$Lu)h0M%Iu0as+&{G%db98?8DSbQH?>J2vc)&awnY>N;bs#PGI2c{EE-GGK3_s^9Q~{w|$`g^Q2xjsBI61gUby>yO?@iJ6EHpS{nF`3t zxX|_Jr)YPtS%WhtZ9U0WBHaw%NZHp`keq$Zyc!aOIy%op%=}O~R1&E7(4do6b-d+ucv?q4 z9fOR#*t1v30x7WZ{+%k)Bce26kSJXkxZe@ndZRw3Oqb#EQ$ig57ls)dVX=NicG@ch z;dH6K(!qzn3B-{b-xW8z0PFQ} z1NnjkHHZ}61WiB+Xh@Q-la+swp8{t9W86Zta^SZHwT4s=odZ)r+uyj)A8OcX&Lm5- zxJrb1zF9^ra8cfu=`R@Q861a{*la4d$xYrK^W52~sMy)TI#}d~pYQSN0$ez#7Sht3 z>L}wR4w0CUo#s?Pp>*m(7n_1v$f763ZJ^P`Fd);2a{@9~qdP+TJbw5lF`Fpb*Y%Je# zLMEAS3gqUp7(j-|KLGKFkdAM_u9fJOQ)iL9d!kY5fxE4K72i_yjzpP08)+v!eQ!Pm z)JV?cLJ!Ha1UybK z=`CjknHGLM4T}8s3qHxy50}3gG&dtawVB)mdV2T`z{s5Cz=ao-DI=M8OgpY$M%LCT zMQps#I$?ulsmvGyc@>O)^DVecmMx?M>P#(?sMpRIsAs&dvG--8-k<6;*Fv}z_x{<+ zwaqlx&D=kn?11+)kNX+VY&Dpf;<3!mBJ~5Ca?8>#LBeU>1+5xvjB5PpfiU5u^6>F!bi+ zLco#Wc$hC(gdbL`R>tIu{5yZ8YPP~;$0|OGzq}M~W6)nRbCHp`C$A5qQ-+VAGJm5K z$*hl2c@URsn)l^J^Wa==$z@(Nzb7VMC+(?{+8aA)E4PNTTT}jOh(@P-%!eoG1#Hwo zx&F6>r(^H`U8`3`8A5U+@BghwiGs}+9*OL{MZO3k^cs=gYX!!woSA9P&Tq)A+wh{n z>ifiKm$Kv~)>BQZ!msVV^>Siz(K7{925x*eVSgO%N!D5pNn#;tF8#i}l)$cAH#VVx zt9i25cvI0)Mk%%QwYs$XgRr}wnq%a}(;y3e z8LY-f_Qv;)<*D?WxPC3-7|9nQiYtyGXX2=s=J^vcxz23xA$56BmJGOOf@GxqyZ2$WMJ7}>pd1}2<3 zW;)49#_F)>HPky!x_HG|p#fy>1!J z?#6Xu^lEA5I{c7fAd!8Z=U0tdQ^z8+zA$5&<&EkP$i?`T_cLtfN9?PH8>xoJZeyO5 z`RKn|q$ddG2!b=ERPz$~W^H?>NevGEMqW8Cr0K0pCfjW#5%pIc_8*)DoUftL!0`Zn ze9sI-Rr~ege`H+yRbXhQU`W!Y{-9pRul0D}mXEL7o_p5QFTv)eJ_u03%P58eP6Cq5 z55n-3d!xu5n_r;a&uZc?Q3Yx?O!+wAR*K=S2{^3B#84nVbvXNwHzGM6-a7mL{0Jmr z0pJmX^eqr*K(rkV0;bPIQ3x44cp%X+?5Cz$PX2_|(vg#q{K(&@0Xo-TMduO5dG#~v)I2MggPk{G-!u$ zQK$Hd{=_Kr@-K}f%JU4}q@p~;s(wAYE?y~H{h{SznuubRSjN2j{-YIusCr3yWX1M0 z61Oe@TuCyfiBkujR+n?&_CM!%tTl&P@>YFd0Of(u&rWDO+s-hf8drVo}k zjJZv4`kT@?LuMw= z$b5qFKBVYHWoWNJ-Uuz7RrXIG>7upoen&p8zsY+s)kge(2=As!9|e;tzaRgdq7WvV zj*Ec;k{9ZVH8u&cpt5dn%Xrq-PO2vN8F}?2UDm$YDav^uzq1V(^Pgl}{BD7#7yhCx zZttaUc~stc3BFH7iCbKXmxOX!$0Ep*MZwe@aFLI#h62liaH`7;$mKx96A7<_N6$V` z?b}~daxsfbn3oWK75-4DP4i@!pqttm;Tzb}90`X_FNPbX|IyT4jyE4$&-%lworW}h zupj%#eYHsb>v3z1;XrjpW*VH_28MG zxTW#uYh`uBdU;M5o0+MXqIb?*n-G!Xl zI`>Ouo4!?jMJ(x=rs$}4zkdg#)Nr9k?m0SUwU<{bvs8|q@sm`hMj{@!{pcN0b)_51 zraVhstJpKY-}00?YtC2&vO|BTlgZuwII?pg;wElxz6R}<9Trz;aVcZ#gt{h+#r{HG zY)LNF{M3Gl2zD_YJ9Pv@SH~a6#JtjjVnsg{SKG+Am!Z|~pn(-yqW*GQq}CnHN~63u zRdy7AH-E9^^Nu~rml**#WQV_PF zZ;CvI9`+l?ia{-OMt{)1%&QO}&y4WGxUj?o$ICAF;ugBwuI)=NY|)2$b3;2htNfhy zqEa+J;9-XP;OdM5pTRs~5!3j(g;$p*KeBs{*tQ!fCC*@k4tF55(8U$_O9;naehr?R zEDmGYE$mQos~1@w@xrdp)W)9+9Qyd`SIP{w>iCFPJAHy(VIMSqihq|MK=Hr&O1$Ui zfj6=^e;oFBndcT`Vy4g z>N~?g1yDPAc1v7*_@yapt)cPuk0{;FPIX&b7O7j*R`)+>;$=`32E|q~2WqJ&SiXlz z;_jjCb!!r&hwF?^$tL3*qCq$+{{8cqZqXMnc%vkhzfse)ga9z{ z7bjzd>|J2eHPDkw)v5d(^B;|&%&JGmW{>=p6Z1cy^9}i$Dn-1$%r&ZEyM$s5G&hS+ zCRp|5$|iKJM7&!weVSC{fHoNpYmcX7Ll-MxwazHipfVfyQ7YWqh<`&HgqBsZT>OXyO9{p7BeoRhLi0sDi0Gy z9c2$k@xEj?l(hbAE>=$-q`Q=JTIpVB?;ui}!_=1Fss=BS;QASuW*tq3lCSu9`kV>B zTPfnlm>7S}(NQ@3n(*W>z=3AanT$y)J)XouZ0R485$!S2p`{u3@@Paw_h=dEsanm(&GO?1IVe3(i*(N!IWE{?-@LLj4MI z1XRLYOcTsan}1OdwrxBQIIV;>~@B-#lcq3|sYT;7u;49vvm?chbZXKVkr@L{yn11pI(H zC;d({Ax2iX-7rMOv`f34^R?V9NOV!2F~QB`n_cHGRAYBe7!nJ6Ij&gO{=GWCy%Fz#4S7}q(T|{ZeM&OTN;lBEo;lHH`dsHSBO3U

bAeq$d1AOD_Yo{sEMhEzi+=K)pBaRMH$Ai5^(9NF;)(ghjO&WoRn zl8gKkO}6~hS!yvvwzK)nBh9kpH(+L0I;gd3Zf^azK)q%WNsNjh-2RUypTP#C2`G@| ztkfODz`4o;;QMt=fQ>sf5U#k=ktMt6kdM~HPeUd-53LjKg)FG#r_SnXrRO|9lQhOV zQis&%B$|WIMI#6962Zrw%sGJ8h^b$LQoTA`VG00GrRZ&-$^3w2JwqT`gRMYM9KrMJ z5{o|0y9WcaP3w>uj)q^()e86a-`d~5U({~bFM_cE4JCXmQ!LcivOfROTnGhMnmE}B z!MV_p-$(aBNL(PJJor4h7wXDWc2Rnk2%%p3KmiHQhV~>BSNsb~#0CfK0Qh~zj)Fqk zv&26j?k|l*`rVZ5sVCG}iuat%%}t*#qPaFD7T@~vdY?|HsSZp^Vh5bKPyQDc`#=5d zNE|V!pF}{)Eaq+X9h`%ll`qcr%cm0X)tt^?&ufXPc{pLz5DvK0!;}4dYT?#Bne)Qc zEt7@gPw}>OBx6DuLymYhewtTE|64f?{DBr z>hqR)OlcVT;igvPy`<`mi6UdRhk6;ZS5&9{Jwr-7c1P<+4Q+3Zod`s;E?s{V7vOFn zS3lRDK|(FJCsWcjrCm^*?L0ww+*KjQj2lY1{P$8S&0uxbx876@G^no(uB^VL6YN)6 zf%4V4CoxhdAAZNPDPv8BSM&hW8j0j=gh}yt982NvcWVBA{6Wvj;&&uX86@))B^&9E zI%`9W=msZ+?+C+|9d~=9`;0ro;{+j|kF~Bpl!sN*GqgOfNY^DkUQ0SdQtO4hZf5CY zxnSpdV?Ea~h;zK9qk0G~P;I>n2~*ti|8ng~=wtKz3&`mvTWFbS{2#+lI~8M)K)XliS6a-Po+^1zcJv)PKIjHeCj$M>N^7)Jw4H1ur#sD|9U8va*v?l9`!Sd3_ziFZ9UMkj?RL`Q+MGK*y`i0z*s4cBsi#|LwGH zf_Fb}j$w+^s*(BxM)EMbTeKC(ZH0DOJP7y!4Comi2`xNUuiCvTZdJ3DpG;gfRtv=QM;M?eR zC)p?=OJrz#cM-r0)o}(9`2v#55@mFIUgFP0HU zx+<1!9|F?$-k;m!!XbBSbDy(Ld#I)~Wv@TJOK3gEAAcWDM;VQJU|M)NuL;jWh$RmA zeI{!~_3PmpUe_)#Y+k*SIhp;MZs3=r1tg}(>*D1Fk;+xtOShjUP9OJM;#&~z_}AHj ziJ&WhUbQK}OUCLbmnkoSiIBf~Eb>r8p)jS-;BF~nZzcV-gLWAF{W7E3>9_gDr;HvV zS=zYwEE}h6n=~7aY)8#8QgH{0C#I|K%LY{16I^{!hTI13pIU5dD)7{jGQ>UHYM5|H zNL*2UE9y>bJWEDjU`&dviDF?{QR`p=y_uQI`ZT|9ij5Z`wLL}J&hq>E^w($a+5M%@ z>Ff(A9lx?nNeg-^qL}P_v1`cB7XAj~4i!Qu{;Z3GyqV_=mtJ5yuNr8zZJ5E}X$pBR zS=SgRaeYO|s<}z>(s^l-YLpYsxr^nsRc3g3T_fW0v}{!V3)w@4kOU)oyUB-lG8T)c za}gVImnhrM6YmW3zES_(a8`!H=fd9BVJNY1wRl%weM()E&|1H#|wLuw&_#Z#>s*ddg((h(Z zd#?K((-|+ZL}r^OYN+G-Nc!zbcDTLQ8kZ_ItSWYyFe#$eovev>QR|8KdA-XO6-L~0 zIwBUidr+tPml+bmR%}vv(~R|>qlFeM5`yklkXxW~P6<6STanh@(H@~YHaPj-3cVgG z&~?MQVbM2aORAeK_MU`PQPL<`nt#Qu-wf{M-K|PD`4$hli0@S=6?Q&~EjG%ND0kB- z>(t%PNR1!WmqE7<+m8 zhE%NAi-t@Q#SZwKpTd2i2q|uK1)>HYp*yZAjL=Rq!%M_Tc0(@ChL)0G(GQwjeAT~x zOjLJyW_h*u8_JXI9&7R-KaKJ`s8QH&3}lg#7RI|F#EVNWwcJtzy^>EG;uU{e-72N0 zSv~#DA;tj7k#!uqNMw11e16jn#%HxPuaOF8-3&H*ZN{X^FxR8W_< z%FfJn=R#5JpQJa^jt3h{)i=hi(s?@72H!t_>jww-37?zOFH=33k&hC2RFe4GH^1VxG`hPLR3Sw%km{0=W6l$_p3`^9Hs9Kr z32yWAJPOc+aavDLq#OE$`cd0~m;x(L(hh_ZOo(jD$Twj!%_O;qC3lIIM$x@*Qw#!^ z)d$4lQ$Ftw-!S*HqXW3+G9?<+Q5FG)t&Xa~jlx+Cmx9oho_n5t9QEANj6d4fSh&{a zqmyqriVFXUhv=%d7d=_gwRoe)ie;#Do`S(TsFs>H$xnV^T%ZY5dHh6qnNZORlhdAM zQv$P@WS-O8kKn6Y>XV*Gr=rK>0901)UnU1Uug6@}d5QriZ`FzV1?vvCPU!dcXF51u8tPx=P8isjfSKG7NKZI=F@E&*vU&V$gRE z?6hDb++@(u>J17{wP>u*C-My(rEAUwdfsHcZ_AfdvsI%QTB7&)&20{w1HT*r0n~;+ z24GCrB%;crKB23`wq+(CP>$x*fO-{yDe9A(EN^6;W49K*`+FydX=`0M^9n=XlJ;u8 zL^*frK;U9*$I~h_TLMZt_Q==2k#4+y%*sB-0q4bWEPV{zHAM$K>wp*%_f83@i7J0Pdx#o;0ot11ZTWJRJHCw z)nsCo7J;ox-VI`U{+!-ou9=|yjNuPYXQ#GoE*8Gl(V&ovfhHZI&uP%SU#Gp}%$km7 zwNTF*zn#6uUY_G_AQ@6&4ygWDpjyIJOwb+2ctVJhnek-lr5Uq!yl(c>N#77t!%>qZbJ;9n2dfNACk=Cozh*)8*PyS6N5 zEY;KYG_Ibj+I$jExL;g)yQ1#%J(P{4jqLn|;%OHr>i#jnL0@Y~;|T6wzj>Afp-W-b ztDzhBUx}D)59v|@J9*I_06Rw9shv_mD?*aYe{RlTenBI~*ZYg#$)EnS;23&|bQ#&% z0I6BnS*e)W))cOr(7f!<^-V}&Su%d}=hY3JLbuN=l&ttTtm&0UvnPz*HJT6=*vEKNU;gaB;dCS@EB3be|)i*sqc@V#= z`es>I_=5%TxOf=O#UzG10DIo@6n{98`%IDp<%8ET-{*;hx|=fSB*m?_*Q>k<-YrKa zY1Qr7g%b<7uM|&84svy_mG&Rq4S+%6&q-j7)Csbq#T-yz;b*DEb3O%ocs&>MiaUUZ-xved@EnyGu?Uq8LqYs_~WeqpO!1kcsr{iP>Xtfdu` z`_=Tp^#K2CG~R|`6M?i;CIMV!PKk72*^2WUl^l(IuJonfz5{nxh^u*vpb>(_Yjb2C zRRv_=pr3q)GY)-;)C!|_`mwJV`JIA%Vum;~@U=1jG_w@bOZ=7qw zE8cwt6ub44BSJeRA5_<-ZWEs1Cwn~g^SP*OKZ`Mm*ZEzV-pdI`d|ubjzSe)!#=OuR zbjo(>AnlnzIg*keT&WMPF7pZYPpkz4%bnQxsW;sW!A$HX4TY~{Z_xD+Lg%HG`} zSAK$SPlflatQ8hERg9`l$%|7|k&-udo4`@wzi)!cs)X0k&#x~#^=&@$m-*(^XTXl+ zJCy&eMkr{c#6jIbb2h@Lp)O<%S6L|DT(3KQ#8q9;>BE=z?T5kTGdOzQugjSG6=3uJ z^mIhm58-DF3_MV|8WKB1>|aVbi}CWxeDNr9@u%%Acdxj;#0=zAndI;Ul>>hBhtNxv ziE*)jfxckXtHEuBLeV?pjV&T2_Z~Z2;axv9Io6l~LDN^V0x{(y=)n%iC)RWa;I^{q z-jv(_@+Ez4$M&#)_Vp0bqf36F_4S%aJBT9Tfb;4U--W_4kz8S_IF&*;{!ypucfq!H zZ4|4;YK!Hr20dGx#vPo&4OBaCSU)8l>O{FnxI@VUCZsdnX|Oq$dG>0q7sesNsmtD$ zxvSnZyw3g}G&I9ta;JTBVtLuggRWXiQ(7VZ9|Bi$yxar@NQ+z=(h*6?7C;d9oGq`96n{ zhupnhe~vXXmqdqjEiuq3;{zKr#`c55IGOnGT%wT=U4b*!p<*?vIKo7KI&uYne0jBH z!BsyzG5pyJb$g6vI|%um1zrYat$7!TGh(Vtxfv&zsGb_V%`Au9+sK8we$4YoD_OU& zrihjDGETiTo+kb|yC<0tiJ0|cO+*UrZK12gd}1Aq5X zIlB&$W3k7@D`@OPJe)9#Z;Q{nh7X8Egp?azjyA5z&`oF8yQFPBqkCFZp`*uzUbF5b zeUXKz{2Y!w{|s;5x1sa-f>Xn2t16-&+EXTz$z$X1z&r8*{R8^joqB!~r$(U7mNn?o z|EuGX3ChUgF9lmv zTr|P~R-OhtfGcP~R_(p!i%uZ>IRSaWMMUWH7}=at>n?nO>J#Hh(~g$Jm3<=cYcIEx!|ht}O!bRKNWaJ->7B z!a2oNtn4S7aJ?}}NLB?4sJ?U-YRctUymp82YFzg$-g|VV>ZenleM{70{^~8}*~QeL zUcVd3Zv8v&DldQCUizc$mnj1hAdm4DNaHV4TTw$pejsg#$t|dhd8!Rq@t5a}(VT9L zBON2_-Be}+V!kV$pL!u{*+5-2Q3scpRP2)4?0im(t?`9%X-;afpz2B$I=t-YHUY5p z5iSTV(nHV{t4K^(Ld9Ac<+!dGY8_rLSMgLe&loiCJFsN?>N}U(WGZ#FbwzTJd>x>; zOxID_K;-dwrha5D*t@nHVgy@c(^*MrHvIMVz!|N0B zizYzdXSpcpe#ld9?6-@z6~2H0MS6?;C-AMW!K+yaF zeG0tyyD^Liwa^kbtLNboDQNlFqolNAta1U$^D5E`Y0|l%6P0RAZks(aQ3gRO&qz ztD?RHx>6)j641fOnV~MWO(=Wo758#|xA^)SZ|{2^3M%K}hTl2zc3$?5zcpI{%fj$r z7DuHm>#i3Qk@(c*iSJ3f<*m0r#uRB$$9vd?;azN0L7`Davy_u`X2n4t8Ts*ji@y!+ z31vryZWx9Dr-t4UqPle#j;QL0gLH)kuk*5)O@RZ#XTh>V>kd!?aXEVP zTtx*OOld1dan8@m8JLv1|Jn=*@19C^=|UlMHmBQP;(`8Q9A^igI@#|Im*1P zkwqp=9`b`%5y(v5-Y4?`|5(2TYUb=@7YcvgoMIzSwLnqu=wGPcaDsI-L7MVbgB#Z_ z=w{3&`PyTCcFWkE@W_8eIZY|Z8ueuE(y0Jd8iBmp(qD#_LgxHlT(G3ht_=2K<+qgw zsiWWh44R0&fN<9f>%Z~m71XUwZAH8&eo}}NKOWhR3h_@#&9F+S4${8PqO&i2H}+ye z-*r9Q{;^M?+oaXGGWSpiFO^*E+mf5DxF&t!l)H|8PY1sUv+4=QQu{R}$+Eb;J`}K6 z;l?|~6@wyuK$lJ;@oG%CE_cz4J?qZZ(fFo z$YlIZpwBn7AD+z%zls&;)GHWVJ&CD<%n4D2 zyG>>OLabSor?KADyo!^LM0k}>b+1W$kt-Z?Y7&02|53U?t+JCxGa@w%NvXmtmP2c~ z$(+SK&9H7FmO51Eb`-;z6wxXRzieydIh8alo^23iLi_Yh!oA(|h0j9&;7C*^L0n!| zsWk9{G=Ec9CcB_)WgMM9CN7#tJD}1*hgN5KxH+;Rzw;EV;5VWY>C9$4hM5d;cP=;N zjs0WW2CsK(kP0rzJ-%~|_iopv4v@zP{IKeIuN&I^?nh$#&WqUG&s;NRy?-vbsVpmS z5)BNwsgb^qCbK0`4MXg}m48|RiWiyf4fHfE^)564vvVpGBr1Q+hcP%^=&=emgxZ&r zL=me&$9;pY7Iz^hn^E~#aCWVlTazDBj4MISg><{;!oTtEKniuC44ka`KY+&plu0;t zg5Ne^S^kZ^!^e%k(B0)*{y8nlg^FF}=V_jN?f5KA_w3}~^jA;mML_+90{fK1{wZG= z)8_tG!04;S!mE9w*J+!ki)ouW_FzLOtPjb^eW9Kk%d`xCqbZVcJD_q-X=WyiK?3D* zEox5br^9^~pVS~RRO_N9ccN#kpBKZ?W2_(fI~HPu-rYjz@p~y(w<;D#Bil+sg2Y=6 zH`=LDa4bFPZr}`|n3xo_JR%erPU^WaJ|(QLzO4Ri_|AjAt0}sQFW%6s<`t%O|I|V1 zniO4l@OTrn-s!t!euSb*O>^~|)A>Dj=PQ8b$ch(bi0sq=a#|QG2s9`=rD&4e)`}hO zW=~6F#QCC+>I2`#lm~sZh))1s82{T0^R75Y;m-sK>-jI!wtqPwni06H# zJJBemUmQvjf5~f)cYe)zTnu@UAY;cD6vd}nb~XTBn{l*n@c1aizhR@5q1W_9f6(v%EBLt3y(^#V{uA*2VOp2joso!}tEbj;oEqh4)zAt0g!bN_%d+4YO%7j{!%~T8}nYJFU7C+BERo! z!nha>9ZZ+8jNCdoio)GP&BT%j9F&UMn_&he++8L>*q*K9&<1EtHuyAcVYvf z(sH-_(Ge@pSqrk3*52gvo5wc0W36}E_i8o{ev=ZJmz$gXMXm*pw(+VfQ@@{7&bv2c zQLa8TOw`F!@pW{3nhxB+i_Q0gDCaqe3?v|M>g>kUs_nRgw~vEZx8&jnjP8dzsQh?8 zN0USZ67Ss_r!_tkJ zsv_Sf>Mzfze-PNT`i?Cf5I$6G2!XX|8hAT3Oa3vt1_9uucr zBI5_+4JkC2N;cF!sGS;_Y>Kp5sK zYOScLGJm^d%Gx*HA`Z>m_KUCY$Kgu!u*$SJ^;> zZ~w2J(^}z*Xo26PK8ynttPPr41;}|#ev|1;O*;fUQmFL;QxyA_Fc1;QPXDPAl_6r@s2@fW>S+L&$ zqJOs)#yeVBHv(kiy+=Zq{<0Nn^5Hd7bb{p0`F94!rYK$Pur#?!v##yVLS>Tq=g+;Q zOdMG;PP|c6rji*U3;56`G~SnF(^5@kAcq*jw%?s893vu@#;$fF`5j4 z`zAO{T*Z#tNvD$MLyRtJh34EqF79uKNsQGf3VHU}LbYRP;-q(F3>COjHd@GMaQ4JSiao}J#B3kll|u)M`9iB%MefmN%Jy{SEry61HyHtM)012H%M zI2E`qJg}^kO((>WbO&v$(>BFqw)1)-eqEq;XbOomPi|QvUENEtZu=6 zvwCYy4cRKGY7o;W?pEHTJzuBMT^OQ_}J zc`rU5Y@4Q$s6@eMFvZR{)g+ z0SyJ8a@Q9Fg1K!6^)BBvl<2#j#BhELur23drKh7y8G~K{emOP3SdW7o6txYW12cmA zNw)23c6vr#p)TMdT;hfq`0UE6%Xhy*zUkOtUzjFco(0tQYBP7c zK;?_2P>?sGTI1_d%ERG9rVadoea!{O6_N=R)mFqY!HG1W2+oS$HN76$CWV{@7A*f&H6V&o7$`Vh1syJhm zBwOAmp>Jv9Y1Q(R%H$d=H+J;bD1CBI zhN7`%$ihf`cVCl;g62$*U6ZL$Bbe3dOKGvIWt0S->!Pb>B2a;=v zA7#T6o?kos*vLY}hDBKXjk+B^`3T^tRNZwQW;?#tSatDkD%>KndPv4V_!%SLwG^L- zCU31hO};s61OlJ%YhGkDe-z<~BdniC)+x*COi%yiEwKC{R^Xb-%NqV;@I3#kUB1?# z#gUFp4M*HizZEs6B(RL+htMk3av`(}ql`VDX$4~iQm@W)+3x==~*8EWnp1YeV zU|H?;0fnwtQwWFYwxG&xc?S777h9^dW*fG>RvHl>A5y|9DkzW(r6Qe%dDcRmdjU1= zj*kx)E+wU|)y6JEp&^Jb?<(Z(-X9^riP1g6#H z7F!eE5)s9A3(~~h6Zfxm{7gC(SX&{BWy+YA7j36h^IMapYSS+_rFAK95%)FJSD4^7 ztz)$dT-$uF%>g0SB`sPW^T}%FW9O$CV-e9Au4kRTf(o6(baK}BOiVH^pfqZaR?#k> z_hEXR6?*Do1tD{GS0pP%^exRZr6zUeTKw97HiXuVw*)=MkZ!wZ$`H*R7SYiYC&q%p~S41#bld{mBOG)KgcLnYf(e-o6;0 z&&>v6-|omf3z3KtF0xQCgXLU!QHYWk)Gb2wJt zO1dS#*;BID&=t=n|J*l+38If%^3ms-QL$0PUHw1z29joMk#ZS~T# z$2E0tu5Hb^5BvcKb+qLck^?xC*Gw2k9t`nL-m7W2)gUC?Dz4C|HkV*8FDZ6Dlj#!U zM%|Mmxb#AL)d26)iYV$y1T`mtOOGzRqmT%^t{)a*ba}qq~EP)*G}S=gd!?| zD{5t2F%e~!WHX8N%ut_MH7xUygkD_?VN9m)^ZFimY|gAPB2Y!FpA~nV6CY5m_}OrI z!Mg9ewX2)IeT{fz{>~LkeOvbsbl1oP@airE9yEvDtKqRy>N%fxJPWg3MWc8gx2CH9 zYu?tEd`Yu+i~XUUAC5$5CmP`0d%ayWIf?nWy`JgS`=$q2$)1dW1{b{t_KCVLbXlH{ zZ`A%TPq+W~_d`3o+r?G}US2XUtUIFg9aF*!-Od@X2huQm2b*VxeS_8?buG+jGI_%z zsWJ`tNOZIu*W`od+S*2Iiz1KYIOUXbZ`E@z;(wnt)-}0x)9Qc1`bxVdA`FMzX~X51 z;U|;)8a33HZn6xJ{0o##?q*Oues&6(sW_<@rgOgxdfqKj+s;Kc{t>2vxc(DiyA-N> z)4ID|v?l4n7~DZur#W1QF_D(0gH@}O=Epe-hpAzJZ56IPCa7d9Afwqor});9c|d?k z`Bm#Xv6ia=_M8tNvs)z7oXMtX(6I;8bfD2;B1BWaJ(lfhxwiZP%=3a`n2w*z3_VNC z!&pQiYtUjucJ$I@FPwq=B^)pt}hS?PloA&#QG`6hNsk>lb^eerjfY1s8? zPT%F&dyV2EtKnCc+Ec*i=(f4SgmJ@dbkn{R?eRDrAEA4z&rV;}I>UPYpV90CIz8Z5 zc1nk{C+x>UEj%3K2#L&`S0zTGt%>ekCmty}K1gMgdnwA5qVb#wmQDV&ujXH>2TMbi z{v;hBW2rR%Am8@Jp)74rb7*_nCp(eqin&^RdDUI;AQctgvz{LjHtYSLh9qG32k%{& zmIMdk4#gSnwK5_Ys18PCXR#Wfx6P*AjN@r*zisjJ3f|PKpz(if27MKFiHKZIingE7 zP^xsHii|pmJG70-JG3?=Cw|c9mGVmYaZ~}G4_cg(4}>SkNJw>N99ao`zYwu&Vq_y3EuKY@nwjsJ&nvM&*ZtfQ=1BTHqO zBwLbF2r*>|$(C&}W-K9l_M!+O>tq=_V^`MfjCF?W!weZyGv@Q${ht5t{Ql>E{^xwp zbDrZ2hvPnT&2?Y*b-kC@aysw%b`;BV`>Vq6a3|9Pld-#WdtZ?%^{2JE!prdCG?+7l zR{?QWqSo`yuHTcz9>E6gx>24sN2}W*S8rcDXQ;>AVjc+=Zj%RiU<%_@&(%+kqM_Y? ze|suMQy;duzb?lMpQ|qK;Yz&0P)rv9M7~t_(>k~yd=zMUQO~cUg`l-#w~651(7s8k z=5xii3EeU+GZDG??}_F2(hZwm7j1~vC4dOPRF2`V0B!wH+IdA`8vnCe+BgsmAcny& z4+DcgY&QmFAMK6e^EEV%{`WloTc!W!#Uk-P%0T|M{-4QqeH);|M_BOglKIbnDszYO zRWw;z=`4WP4lBU@W9@-}yrioefCZJ3-GP$s+^mAY@sAvjH6pUfr@C2mYCn+yRLR-uV)yFg$Q=KPnxVTSFVd^t6US$g`4%2qcmK%HSRut@MZomR(a!hy zg0dz0%I?$er@d@UyF|6jps63D5R$I-Yv!uGw}s`>O~*~hJ5mDRr~#e!ECyT^^S!e;15;7r7;Olzz0SI4^#nU0H5d-PGke!aCy zM*1;~uIZ2V28I#m)vQU+EB;v`aA@m$ukV1)!sx^J@f0O(9e26?;~vA{^`iqz|NOaG zEOS)A<{aSj=IyF6(cvs6Sex`MB(XAh08}hCd?RHI-rpZ~e_t!q&7~>l5lSMCqc#5) zi*)msp7NS~h?1<(IJ%{@&*JO@VurCD$fm~7A?NPe+$r~Z9q_G~h27jJRvs$|or z4p;4uTzs!@mq*1P|CKAc5bgJ82*~I^BLJmBw*w4q1$#d#Y!hmZ6{Ccr|IvhM#6j*d zqOeRhRz{bn=E*|ntGg7R!s70~Deq|#+XOwSscjLr2~!ECI}e-R=g~1VtDiQE8`W_+ z0bE)snLYpfZ>4*Jz{8BC{;Ul7)t*91ZkrR%f7q<+znXf|*6{qC&WseY@M~ecr2HY6 z##I6=Rk$rsEu=4@hW>o@5N5KcK&-)=je}hNh~w?scf-S zz%%gMzw*GKismzi5j@AI42Z5Wz#rF*jHGCJtS}r8&7?b!U*pTR3XO)$zKqF?CM>>H z5{XLhUhFN3?mT^2_|eJLXM>$84@^W4di@IwSZd$AY5>>uSGy+`@n!NAya=A()+DTf zQJwsI>bd`a6_BmMYPrE~FDziScJBX6L-+s0TmMH__ka2u{aO6C_S&5Dk~M$6|LD-b zY^Nrc?HM`E&2EhKgX!m}N@Of9iEFbLGmwC)vXE>eb0eHNF}bBHCft7;ni@@Pb*cO;plH~@d7qsT_uF!JRTIYR zmy>KN?BzS~cQNxGev?Cf0&mhi?R9fQ{Lzc!MDSi2(RP+}V@4gx@k40WL132mz;>d| zN=CkPS9M|D(Bb+o`Y9EtE2nCo@Jnpd*iVcE*_%W<>PaKsBN2T60I42d$Re{dk$)S5JO4ebnGZP45#Qg)Ro6V_Qf74)YxzP+S{+#GBrc$OodrY zqNidE9>u6gcLsh;ebRC9w5rXvZn=t20d(kCdYw)to7sR*@ty8yAK4i{hmt3ue2l>d27^u>$Ugth98No?pY z`c;-ZO5ARKUdrNDruX%SjlUh`!T5IA~rm&nwIjP}yp z%H#LuE&Ne-d)K#NZ{cE4x1?Sas?kd(O(U|Tp&m_^RgRgEFGGzm^ycX1yQ;*5S+N#P z)swk=7}M

hJtDj@MgVjY|AxNw`pc*S{t;;R%OpoGH3H%MV5C9lBp_ zGBt8%1bg@7U))*YLVe$__q?q>wYQ&Rn0|jsBE_`kLzm>zidnAB7eTDm6}G4JN~;FY z|LAt5Eu;D-vUoE}$|xP<%`ej)!YT|DHSP{>ql8}uB3|GsEOM%(#5{v7Y&`;kCLujY z{UxvmGC5Ce_BSYgg)3lA0o#&qQ4`xu%CRi&c_~z!`fhYTocTya$gr~KWrd(XHGfCI zq_?7WFz(5#J3%3t>qNPR56%udYY$-SMp}1F_;)W|kGME=Ehace!&^mnoT%Jw{x(mN z&=9)@p(9sxn-RJ8m*sWf$BJpDqPCo8-n&|>ypLdE{R&dJTZSpxqR9Jsx?FfAG3-_j zLt)xDeYCh=?w55x`MAI**A!nHZ(=hjclW{TjKcIs9tWKk5cG+&n^WX;?CB4 z8pNxaXR5B;0z#zJESLJL7opg1yG!=Rp1_wkO;~?}_~gUv*PW>)nwT+==l$mLL8c&9 z+rZ55%hXWdP;vsGOFalq&*QWDkS2|~#qAkEidC7*g{`Bmb0V#L4+6QK%8PT_xl`EN z1ZeD60P#=4Sf_lLDamg_jr$yw)?*#%Uf=$_(9ZMrfOFxKBgs^{g~U6Kcf2#Ot>?H0 zk>tzSF^K2rM9>8||8}D;JCVC5A7hH85=Nw|8!PNl=s0ydD9zo;M$#>+vfSl~#_gRQ zI$kEpzeqgL<74_pZYC-NB=T57_UcMv2R#+oI=X+z)U?miq<4th|IsaE|1?=2<5avP zpDG(*`B?jo{rZE!B9Q)R9Qvg>S$30p0lB);4@!VA!vqN6dt}Qpo0lPZ#V)~|jUD11 zrf>HxE|;mLnPeqi&StOqk_OwqWjk_Oj95*F@PS|ClVt&4v3Onn(|m0PpbUJ2To&eA zshe5-bip9FVEO5=m(;@My9aT34+QxQ+~)N?pIxQjMV|qb9CDF7h%RYuK&QRWfp5Z@ z98I7+N$FADw)G!w39JYOXOldlJx48gMdv#MWxj`19Vy)eI<7L-ePau2YmJF;Ve%19 zy|i1{Jc_2(aI*?ujA|Vych9m|Xq(w9L;JXMXIv0?SN7Q+YLU zED^3`9l|Urn4E+=3s>~JOE`!fzCDOlgcY4x8an^I#OA`H^j>&$(O~#Ru>=}h1yP2y zMVLVn8nNcRDUpEOH5w@KGtLiu+3irswrp%_X{l>yaI5My;TyV=`QZ}hPBrVI&xh}H z#9ec;yfQwUa-TK}5dq{Bc?jU}05c=(SRzXPB5HT8?X7>65{4A&mgTMCnPz?|>FO;> zJj^KEMg#F`jI1yVfT_GZkU*ad8=IiZLRD)f=%!y-mL{h3Cf(N()jg!@aSVBLsK_=2 zN3u>!2DqctNukUTR^o)#g0+k>%G@I_rF zyL$m)R)af6#Jr9jDH5oGy~yW!>XGfh z)<)(Xe_3=t=G9N;j4fL&f&BY>>|G+ z1TBZe=hvRy$iXVGs@FHhDL=N>h2dzDvTPmZuFG}640_EYXa`NG4KTAa&$i0CZhOP2rY7V2$oYFe-vC_-(p}uG;)L00 zP-7{T_hc=*A5E4sO-WLd|)o*QWUYuH5@* zpdBZCFZ#hZjZx%zaUZFq9==Y*#0n;5Z8@K!NE;QqPEIPA3}zbB~W+`%E_)Xy(5FX1;MC2|{BpC4kq8PXH0ngVmt$de#>;9Y3|@5vBc@d%g=# z%k3Q`UzG%Jrft#}S^K0Ge!oy{zT=9|rD&JpohUL?ee$c>hjTyH%$RpgBe!Q?+m1;( z>&tT$TbU)^S`YV)8li)W!{@muJ znmai0`+GrS-5QQ*z5vKuv6}j`TO1#+Hv$o|n?)AzMv$hiFfX zaiL}2DoQj@Kf4)$rij*@$2&=NDR$fnH?#q{i(5Cq7eNHWv3P(V2BDt6MtVBwuPAfJ z8~B~B9$L^ws73~OOO+IF=)Y7_9Bxf&{gyUyDaKd0!dNp|mA-<)0(?kEnp_|7B(f3E zohb=4MY15#VgSb!wdgK(h3rxxbzGh6Uo#@!_~~7=Wi~|2!c6yeqrEmEZ!a~YT66rfB9YLh!*bHG>?jd3$|vieqW0*oj6cU z8KtQ|dVfzb7s@^^2zJqF> zw8LK;x$J1vlGO2FRr5y|i{K6%7;%)f_p#YE8ru!x{Z0LJlUUnE;%JppzVz28PX>Hi zbl*E(%e>U~85^`3)p%W^Dohc$pI!jlQye}B;E&qnvFMaG@M+YVsW(35+I>m@YGy?j z7nE@F?K@F)a0d#%HhKe@28drF$daW4XjViQ6HN=oF;jmXCP=*2pLZqQYC=(JM)8H| z!s8TUC&3(yjfAlhG@!Fl~0DadA z^0m&E^^lmVqR)&igHEp<#pRjOVc$XYNAI|2AOuSUSx%-e{|%@?peKL6krYNai48E1 z7t^h?H}o`m%7phP+mx`E7i4%nZpg8AUF75QNgNs2g!FQgCGzp=#37O#AtmCs_q;iF zHaq|sTNl$Y`^b9Ii71owD{Rp8{zQ1q<>nw07t9lx?4(wfrF_73Z|>1rKfu0X;sG?E zEa77<%-L(+=b*}FLibAnto7_t zB^?g%PBg&)5-xUyP#V=H?7rq5$1m?x=zetpRTA~3oZr1vCHV9hLb8ZxZBtE*WL zli~QT_$wb}P2~y2mEr*i5I`!(hY35CZigU%hbQuz;1>d|IFvDK7I*K@%<}8HPG_l0 zayHajhAYoMhO^MVg9$eI3Cm|s+dDv15E;E@9uDTO2gl2Pn+K|joX2ku`lF$gnd{PS z-|hRR`7+=6%GDZo*}POHhqwaP2FAc(Z*|;&(UX-6sPZ02`kh8kmr9QZX6)N7VO3ob zQPjQK5&0oHmKSu^T0W-IC;znjNC3ID3_cB~Zz2Zm2@}j$mt(Q>&TTVo_U=*{v+ouM zJmu|l5`709G&D`Q%nxpt9K7ePasYw=Al&#D@r(h%i{%O?7iXQ&rhJ;dn*Vlo(hR#~ zM7cY$MlldN{>3sFZ|g?&B6CBe08zGgL_V zLUD^bQKzG{_-()i<5B~qzR#LGe*{xM{J0d(uK&-g?-`gZ3F-6HA_bq;(LmHY<4y!~ zs?zSPu24t%LsWb6!`%L4S37rSXM^BN3e|jXWf;WQx-Xw5qP>xPz;IQ7fJN8)jqZg; z!a&T1&9j-3Sv>E7eT2GHTk^!>2Wl;B@4MtXL&k@U*IzTxg>`B6t;B->faq%6O5G@m zP}&P(gDF>OM#2=`gUhJ5(fmU8Nk!W#ULRcCc^J#PSgp7OgRmxaAQ@nZ08X3s9ZKXQ z8oTyN-DaOMFHDeJ>{01%v2)WqL01IAQNikx%y1)R85rs9y>pUau3g4*0B|{5bRL$l2Qu8ET z&RB9juArko8KCk>tZDX+%}_@+q9`6*t4h!H7v0FRM)X- z$$ti??t0xc`i2+VsntxqhdHm?Nl0LU_t{@bRAoT8>ATR4wE(&AorK3_;!#sVG#GmI z9r~LaQIpC)X-9Z!`;%zi15ieKg{oUi@74v+bo<`ydBGR)$K}XE@Cj$qjMt4(W0nIT zR@+jqK+wDSlCwhPkvs#Xoh_9!z89-DT6vHk#|soJIdkL7B*Sn0*oC}B1JU{%ur7d= z|MUO@SsNHo#+(r~?Y9bSX$yG~SBP-8x3Qb=lX<_@S^)jU#wpKk7!fY@#g%hFol*=* z&O3LSfbc+)C6Iu-<+UfLc>w=AXicCyC0<~~ODJr#Ktw?3Ij^atN=w`L%^w%D9xq24 zFKJ|9D3-T$(@#riLS#Q0>%X(6hK$1H&!60>oAj9>vH%v>C-`KZBH11X!D!(I0H+cxa(>p7V6a^CW$ww)#c5Q+S1Yje zy}G*cr`I!-EFUu`Vq*_LC8y;beDJCK#seyk9WFK+h9Io1QlDyhy?1q^*Sq8~2N$CC*v|GUlO?!f2`v3`FlC(GSga4(>*S&*8IyP3tr#ezuM6mZ!LcQcPy2UgT}T(tmp=zT=z~Ww`gO! zXMa-lD`1xhr};5n+7g^U=NB-Th5BXN+tpi~(pqH&mxN!-70`tn(-9$a$`8oRUlCnH zIoVX@vzk3ELA@Rb<{_p(KTSn74%NOzJkJyD{iyxilkOnL0;c`4zu}r3^}*((t%~VgZt&mxd3_e#iu)mizYakDamgQsUtOGSrStCWNtrY z)T?bU!Rlavf%J!H-Iu}#AJG*d`?>-J0EI(2r5eE;fG^4OImrG?$4u(l+on5*(|6)q z=f6z@^TMcinuqZ4CQAdjTO~LfG#1Mh?MtY$Bw8*vmqX_RDuNEr%@45G^Hg>%IeU1( zuB3$gNoh9ZTG1u@Qg~_e2!ctw-Lk?lMC-0g^32LS`MY$hnf zu4YNKHO9L=WtJ>^-R#+#0_0CrkrP1j-<7H{yO#n~(?K+oO;QH1kz^o{|Ixj(UI5@! z4T!I&Cx26}omwA&TaO$&fS5lLK;U-ippazgl|4<5RrCdW+eK)d!}PwkQI&y*Os=rIKEPS&POghAaR~ZV@8HG;K=;qKo2HBhEmARGejAxtR>-e2pWY4 zc~Gt-?0MF6*V8CN6yJvVP3j+Br(b8 zh4ZMYmIVXPlHm`AL4O*b8mqEiWLN1okD!f!8xQc&6hIqMxr2vlJ^@oEY#fmg<6d?q z$)f>5)84qyh49JIt~&O%WoO?Oe_b2d!PhHBG$6|{^-cycB36MSNxf#5PG}dghj_kU zSA0szU-71nL;szr)lf4H*^R+=Y085wm0QaJXVnpk@LZI{<;8K`rW12S&vfl<+ zO?fKXj?N73-a946yIYvB#m^Wm@HYSPjWlMd0d|``6(CupaRGd;o2J+>H2ZLB`m3r=F3C{P#e!s}GbkKuXz)cv0I-y=j0Q8^AO5sBx;2s?yx#m!Y zcPdfua6I+kba)&WRU_X5xwK?yX{9bF`a?Y54EWq{fY1G)Lkm8_uqxAK&PMj`YRSf5 zA;P=ok8N_Ua3vIuT;jPJ!TILfQQOM0R_2}X8mr|qWJu@OJcKj?T(1NF3AnG;XLLJT=Uw5OmSGe3&K$l~8GgpR9MN?b@eB!^shjcO z7k~hW1zkkSzQO_}bKM(uaC&;cM@$M{g&YQ*_ zKwRkp6CR>Yvk~s6pe{TPea^kA92(?6z|`vbHn1saH!|YgFxOq#7anFA$G@XYA-65z zPiTEBHQe~K6iYxfe-HdJ1tZ)QBGXMlFmInNAtQRS8h zvsI!kiX@J2hW{_3*gvuu@VWu|pEzfQXgBUZ;xr8)RcAV(rT>5cb>S```COkx9Z-b4 zz`xT*JCv^iF!p?)o9nOxk8Olnj3T&v;}tq)D|hJ za~cHZ&T{B`|4H;&5qyR{Apw)2_^{|LKSwjW|M5@ zN4_o_cza(+AJck^i&s}TRJqPhx1Q#6RsMuRV+{ZftKM9a6xqyo4J>H$1;GpR^t_nG z49UM;b3LH`h9Ga9r@PUuhXQ0a@60tW@zP9}!%zRw1xro|*mZQIE>W`|ZIjI}m3>$% z;(+ff-q6j$?~ot}eY|pfKKCoQ67?!k=FPF$%6y0H#J+!SJ3917QydN!iuZ#I2KB8( z|8497#ltTX;O%UUGukR&$4VTebs?%UvUmTM_3LfvG5UD?9Y+o#$k&0QM{p?dGMnyA zGK-lXq4;j^<#ld>60$kl3Mg9`Xl(O*p zva}EcucUo0y|1=f)@38*vK1MhyuYu-VIz}c5OeXt4~b^X*U!#rp>*=Ufb&w$E8*){ zSO9Qv$I^6(GBLmB?^z3t6sP>XnL2es#}lcUd0jigCM(R`06I3ny#N7v8w~KXP^bRc zzq;*yrO>Z^BoF^&wdOY_Y#(5Ku5_YBjAw!?^J5T=u2h8-G4~J0YZ4tE`jy#ukz@ufnMu}OH9{a$L08a!4!9Aq{$X@GUL09 za>CQ-ew^^t5JE|N1mpmSm7H^=KZ)w9B3n(EwvMpxT!{ZOO+S{U$>(Lb{5kTLxU)0E zf*Vrn^aI*mhnrMPeho7!=n3+;U~OSD<{8j98fnnA=`O)!^TcntX-R-|LRg8@)*=eq zO#Tjgy=z4F=~ME4Z1C&l+4k&nFhHm+Fl$qUZ&a0uE)>PL0T(+hrz)4hM*&@`<0~(I zQnmaz%o4tF4#f#w&3*7`c({q)?_)f%)tf92j#+q78U ze(yx`9igAOD`kL z1va2Ws1r`P*8{@l(Zc<-smt<9@klDTe=!lIpf!QdpfE2PJ)c}hC`ZoFV0lD6FizX-u zgo?Fx!Af|TWxFgxOX24!)I~MVo4CUr%SgnIJ*c_vO%Ab$cy7m!K>Cm>yupo=%KP^FfhJi@V#N^n56)2+j zCaD0&nq&9mchiKSBgkv1I6}ev7JYRnb_{5VcIhe+j~inGMr8WLSf;%{IW-9VdKYuU zCYjHK^KKe+h_2%|{clUY^?XspD+DKP401tR8BojC4-+IB{cT-mBYCK_NxMizaHOf; zx-K#cAvUgwr4YEWS!K8?-*vmQN>}@@e{*RHykw!q7;f`EVwRz-lkp$&6k0WlZ^cp| z^STSTHTRoc<6h1xRqqdJQ>-l>UT{xzu*vRE+J%EeP{U?y2M3jxfJjCu%0FqI)OOCA zuL=#E3= z2LQ#k`~~G6blv^3Vt0X?@ERO9uSY`1kjVs=*ZlIPn^StH0_#P_G%gm#L~mfTxC1%& zs_l{JK+l2LRto@3nwi?1tgh4;dL7a5c8xJcRfUE9v8#L6bv60}HS0m3gV<&1AL`B9 zQ}}~0i@Ouj*BzYtL_Lz@gEiy+laqZ3_5pGKM_V4XKrJ@Z9n1;mCR_JmkZ)l2-axG6 zvN-e3{k7efpqT^VTG-aGE@`n^)%f}DEwN(*7@lxKV>iVr^HB{asmj$*L?4ZP3gyl( zuQ9WChv(j&Px*b^;FEXs@{KZS^3yb3syBd$b@h&JNl8%e(#{Hj_nzyd&%qfzD}T$z zAo=ll^-Smk78e7c@3l8gG2B9+xO*&#O0l9NZSsCnUr4iBuwD1_ENAOe2Q!~XjYs>S zLFm=bd3B4etWKz6$E23!Ua$L)4-1WuM_c zd!C{iC$#lG@8$`|j-e+}!WJn)M~PSeAeb@BbBxmf2XmIxNI1ar5wfj_ne_r8Tq%W1 zBOd;L(+%^U#9#as%w{DnYs2zr?k7w6ID`#`I7L^%uX;2nVjIYxT!9F>Q8%y-f!&qx zP^VpsAQi}}7$gUNvzWgzz!;{#ip02fJ179XQf?3wzO!rj^Y>Ye&=1%biot@7Z@v?L zw8tF(uP7y7xx6L<`~N^JxQvj z$IN$iiRzs>*&Co3Z6i=JJ+VZM6sNE5q?%9=tz!`h^K z*|K8lR8pGGr#fDGRpZLr(cfi$3eo*FjQ^9}3n4u66fh=7-~jt>GWS5vud!(F(wg?{ z`}HPkk1nJL!a;Ihiq6@Ia94%MbdD`>cZ2Jddy+hQ9YC6EG@h;bV-Ak5Sy~mZB(8tw zd>q5NVC=}neVdg|tjYko5(P-5iL&$~>cq;3cUSIC?d^+wn5S;N96 zNIlc~;N11aYa?_)zpbOF_NT219lY~fCrPUa5f9tIsscTv`hLE1El?^H_p7yDLfKeq zYF?*{$|(l8pzLQ$sc>RoddW7NfjHfsS0&M(Q%Sn}%2BrR{x;v+onrVGcDld(@j!Nk zF(;7#8VHbF8WWZqz02&EUp`dqt+ka?eR5~?f%b=Ip(@>xKJ7OIe;>r5c@+SgAe9+y zOJ9-3EFty8?ES1hn-T;2l}nNgsgoZcuoxJ?ihyPVlQW^mE$eUJY&h)_X6t-MSJ`Q# zMgyhqI^yb;o-;=W%AWt4lNi&v*|5skNZpKF^q8e7YTt;T{D*f zK(UU-k-;IxE_5|wJ?VsQ7xh-0PyDZQNIV_s?*Aui`Cl2#Avy(A>m3URlamLY%t%dr zL67fkG<*wR`rg30&wU_v!A0?7c&F;Y1HM|if%1n>n}+!~?UkM4;xqgML*2_MAF70= zubAfL*6B*e(%7z*Jt0dEn5PU}U4-aYRQ)xckrvibH&->`YpJnu4NFyWzI0n@(dUal zx_+QD>>4&X8jI)dq4A~n=VZrK-;prOTY07VHyojkGBez`TKiof^o5Ks&&o%-D3I+# z$Tiv*myE5^EPm}|6Zu_fp!0X$!!n@iuf1BD4jA8ducX-WNGiw^^#=jb^gBWtOlh0R zL>~X~59L6fgTBCQs{t24)5f|<>A_h;(h&2By-#OyodcDI$qx+iU!m{>EOS)_bOWX?ShV`1FTKv<}(RU5sWC?4dL<&<1$E_tlJIlda)?c;q8 zlT=?fUK%hItflBsebiCz{^|VKg>;7z51PII>?C#z2kRnIXlwec6B2`D z9VVTGI4#=NZR$GS0sRn*7LtB6o9u_^1QG5lUM+sJd~?=I+r;-pE>nAkr_(F?ozMI3 zJly`#g^5QQ$P!tk$Lak+owgdWJ_lPAQjv1Fl1;ddZcxb^d9)c4uzRxaLN@AZF%Q=U zdwP+VVAbL+U01%?K?ydj^V()uO78TweHU@%Mc z@CJAeL}TkAw9?ob2{V))#96i0?}!E&yHAfEO?fem7L?ySBXQ)+UV&n``q;qIe)!gq ziZ1d0b~*ar%!B_YKVO6YCx<0Va+E%b2;kC7AO@ra(Ywp0c5hEIk0b)*hBCzyBMtQ! zt#1&VR?VXkt}7giZXMhc=vuXfiiY+qQ2mH+%Zod=7m5R)XC^Vz4~X7SKbos7LEXc} zJm+t|((UVEw`_%~N7c_h8ZSgxBc&KL_e#&deiRS$1_YJCpb8XM$ZSfQP5t|2B@h2gv-Y>V@v zeQ&rI6RN-NDGZ63&sXa@@rN3T%cu7ghO=zs_bZcS%m>WFTM}nzGGyyDQ-kOR)O5$k zlbKh3$x7lYJflX22aqXyzQ5AuI4jqv$YOiB8pue1GL_By$m~x(LoPHZ+i_!O+$%i{ zm9j|sqt=$Pjs(zqxTF_E&poOpa_OnbiDRsJj*<6gj-0>eNr6DFpavfY)d!Y(H6XYH z%sIAeQ#W41Ws>T1p&7r!(ztIL{6|;qi+*^F+`b)uT;rhN_>1HDmZ5U17bLnc7z*L| zR+G6qlacM5fe^04LCDgZR37_X*?<+nmE?$mht}B^sRQQnQUsL;&VytH{+wH z*%JG27|SRxU;4PEPV+SN8x^0Ej6{I?oRe}D>I{&)#97(D3iGL&!JSjPYP29#*R z_rdCj)glPN+ZE_7xS72Kcz0k{Z(!45gq9O?LPx}~>|1{mV`J-1rxk~n=WJT`pAwjoFP3JXlv{Q8_xi~%0DZTBULU8%Y2^B}#LkVhbsr!y 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 literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/AUZA640701TI9-tax-certificate-1767252856.pdf b/CSF_ejemplos/AUZA640701TI9-tax-certificate-1767252856.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3eba29665922d10e6a31f9cef598c1453def2fbf GIT binary patch literal 139478 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 IP#`A!Kfj&*9RL6T literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/CAS2408138W2-tax-certificate-1767252858 (1).pdf b/CSF_ejemplos/CAS2408138W2-tax-certificate-1767252858 (1).pdf new file mode 100644 index 0000000000000000000000000000000000000000..1d16aecdb111287c41fb124d5880bf1e3f9f57fb GIT binary patch literal 139382 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 Rpdg@{&B!RLCWnpjzW_#2CY=BP literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/CBO230302410-tax-certificate-1767252857.pdf b/CSF_ejemplos/CBO230302410-tax-certificate-1767252857.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5596fa5a4d8354685633e4d889ff91cc73d32eaa GIT binary patch literal 138058 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y z*E4iqP}jVco5wEhI`j~L0>u)sm6U>x0qpgz5nmK=&Mk!Mq}x0R58l9qrE=Y%%uCcD zcU0p~h+>MfJ}6Grg#C34B`swG`dC|`usCEcX4d6G{_-MP;t=peWWxi@k8L*)Yd{jX zFJA~*e<0{#RqPtolT~hK(&!s`w&&Z~*Lpj-R@#x=@6W>X^x5rh*YsD2_=cC4UoB_3 z#Zqiy0Qdap3LPG8Z0I-t@Y_yms{xV=RKhZy=kB#+*L3S=281>O4pF9`0k5+4yaJZH&*!NFd)M>6I}}!6ey5Q%0dk#1O^P{sM*Wc_!H_jk zwa+IvgV)$(hNy0GIE8m}wJ#b_Ah318}4v298Pf;<|tc}1OT?MnR)QI*J zA^9y!l@EB6!migxe8}Lvcp1-_jxObE23G5&-B=Q+UZ6K69y2of$*I~tWiRfxslVHL zJ?FT_iPbvyqFhv(&6^Kh3}BmWG#gB_h-xyW{oJEyz6a-NwP?GvBUd__CoVyMar5FC z0CI8=@qiq(ij0OytSy=L2|2_%mF{9XpN9pX$q23Lz2;4~n%2qd`|)ThhmL$6m)$G+ zh{iTeEX57P_`iRVVocn~C5h+&7?_8 zC|%-tdGc3($2QNJQ%OoT5afR=4nSuizvzlnIS7b8dsnk5Z6hpR^GW;o=&Oz96hFNq z=Cvsoy{_-n5l?7ix2<=Iwq|hAF2B!?(2D|4rmwf`;N6bc!_VAfh-5LrY4tI(WPXoU z#l>GaVZr%4gD*4~c3 zUg5=*3+*0PAYS-2*Hw?1HWk9j9fEh%CBpuP;(5$gAhBL$?#&Xbp=2%o#&@Y?z2oXL zV%6-IfnWGBc7tN3!a4RUX8NNlwC908dpkR+_k>2m@ET8|Nq$`Mg<}N6#0%ZP-tU zwE=dZpi#zEk=YrV68RbyAqGv*pGGBd_5Vlrkg!;xk*yscWOLoc=MhVy>J`ApFg_ax z={D!2I{ie{X+JD<|NASg;dkw8lM=ZIi@-40ki)RvbUPu4 zxHgy5iVluX(w5D3k9{sE=e$YG#>_F|d3b&M;Rn?$SnIu3ARb>$E|S`erpgy@Yt~+Q z^!lsw$16L9Mhb)6KCH~GJ9Okwk}WwKI~J4Qh#Zhl;o1GG6mhn1y^J#_>9-%)5}u8u#IGxoV=T;S)+D?&*<_QByE|N4p2nU)%-QYAXHtX-6jsiPNs{AU zs|`WLnTnyRg%3WzHq9~)AGmDz8~J|h?J;N`f=dCC5@CJ9LEkd_1PtuX6;VC%b>UTl z)l%D#_<)U|tu-dJ|NS!f6N}&{0}3n}#f9_ByBN9Xnzju7w5c=08b=XB=Rdk0 z@_7S=7cKyWl_!{MK;-}6QL*h;+q$r*wEn_E(0q#L7U(i zN?VY5u-cFan*fHdt$Xiw^uP_7idt2M@aYL8)Uj zF)> zup6fz;2^5x^uhze${T+jnFlU%N{$W8&MaT8U)4q}p1F1lBneb@^mWfah1r}I0acru ze*sPosp^Y~i`0gyHVpwa@@+d~WO2>GuX=>AolhH$1+IGnX$%{->+AT9`eQW5 zHcetYK=kI{QH}&fTb73Qxa?oeNmE7gPHF6Ex*7BYD}ePUYc_${-A4Z=DuNmZs70l# z-lrkkh`+DYNgvU5K&w`oY!8aG>uCvvTJSIDJb$!)<%qMn-;ee!Yj5q zFXW&ng#G-lZXrVbS^Td7_4c=Ghd*VIDXx36PIa*liG7Un_RK<3Q{{ClJxITMW%L zs+p-j&ba^WX~{1e=3CDfMR--Uc(tYx7h!w^WXhgesJ*Lx&6MJAHhy~@DGP%lS^B$2 zbM${6u#Dw5DtE82KxfxnW7IDZZ_i?py;T}LfuGce_F_VxOXj{_|Ek~qg<(S~;yJ4I z1<>n8X(k`1V63gk&v8|uJv5!hnp%(dD7=KzYy>v_j1PU4gp` z-8yq>+-sI+_nGKm(?k&Mk<=DE?p3FYh1eDQ2E9`}_iCGU6N7mt&mlzeg?$k0_ zl$)$NuevsPY*(xRMrl(WQuE}&F#b{V3+{y6&@h9HKR^2|r@r3k*Z96xN)$@Rv2>fC zr9P$sJoz^XMs?HZT2}!pI88CU^FGm+#Gn405CtSE&dwWfinQbndd(Pn|Sp|!T3)x65^S~(P z6@PV8{eubU4Uba3{4rpNO73hDA!s?F)gHg4jN&))n{*LyQ14Z@CjkLCaOO-~El z6#UEy;r!Rm=B6pW%u}9#$ydAIY^W)j``~Rm$thzNtzQ}LX!#WIOJ!1E5#%E_?u}rR z9`ysdZ2!^4&)igdWDdt14NJV`JrHp({UJ*iFziMR+J*#BRmo>p528ZUNAs}=uI(0` zs@1;ENO0cj>#O^(@rfUuKRV;8fPXT@qN74MF_7rrp*jNQO-&wU;tdvj#d774v|zb- zF#Ro%4!my#mEE1s2LDb2)wL0&qY9q71?t`}DqXr)^HjStUu8zQUFp!&g~2tDi&Z>W zdC#|K$&?~Xn}Ca>YI5%r^4d#xSPUX&Uw$_AsKpxHV@kX5{reyfRl1{+sJy2`06az| z(yh=0r8ueIV;jcfMYq#5Eo^uqc|+IzR@7I1teOb|V6&M1Y$vfO^`g1*@cb0Itjj3Gg#8M&M{4;AR?9$5)~~Cfm$PYNTve#-Ke!K#<@&M;@D@ zC0y?kycCF4!T-_yC|yL%h3x)EXV3$=1J%NM$HM4Wkz^yaFtNdb+Y6U-FWl?C^Cp{7 zWL@LF-foVrFjawY(#_4Sk&gW&J!?2=`=UvMwE9N%DqW-G;q8>ca8aT)uq(xZ_XL0@ za$!yf@csYyeICB#Key9x9BZ}jG5gVho&E}_{;F~m zl|cg~oq(l?UL~glk;-#5^`m-|*R_2c2K>igh1(^cwgdG1&mXz*iVZ!i4L>Q9zIK#vX;OHK!p zizEWPpmYmavf!@y(?CrnD^1PVA=K~ciyxTL$45V#Mb^|O;Y;}_%?P;U|JB}CfW@^W zjpFW3u)&>S1}6~Qf&`a9f?IGWXmEFT4G=VF2tk5Fa0yO=I|P>y$UEf9-p$^9|L(v0 zzPImv|Cu>6-PP6IRV}BxtE&6>bh>)jMy&{gm8W>xjn}u&Pr^7V2Cbu4w0nmr7Yn_X zOjb#&YTA}@#(kHGZaZ%WCVC>)yc71|77s{m`vHoZe%8`Alnqz9l*PYtT?v)rCHQ&{EA>RuWxKcSn5#YHb` z*hDbrr;C671sHM9-2^xH1xmcskD@j^^-0{zZt+Woq654Qtu`bIQ3^4s7zJ3@MJwKr zqJwT}Sm%APzS-xjQ~@vA?VaN&G113yCzEO#`(hl6%M@i~N1aM3Hit%j{FtHYwGj5w z6RPE^pnc~j$dBHPpO%3-&@G6q9S)3!V#**QYI-+s_STm??^od|Vh20V6drj9TG6Y= z?Bnd|d~44nPc}YdoO&T5%=SPCcz+w?H~db;(UkTkYaiHY#HkeG(88QySG+}~aWguj zvDjOqVAI|Yka#H%w|&pU4^)A`3DLd?7QA1xTB`|LU)nf=ZK4euxo(P<>dap$d=O8F ze1V1h@S0(MDJHPu0dWqZOz!q-M|pXO0&^VXGN2vv$D54IGL8{sA+vOlANY_i*)11#)sqE<-2p9&96v z3^j#ZV78;xm$DfDY{GY!L53@D>+X|!Nxgi}UjAi)D0vQ4o~OnLWB57umUdUOmClO| zm_Gx9M`gl_4b8~oiDc78Z4NcZ@^BOkVH6B$Z`54OH3yntz>3-gcQl-Zf>r*_8239X zR_!j{1_w5r9@P7|q8g+^hcNjs*#5;s8vd(89R4T#A3yNof67XV0QUQ=sjZ2Vv!kh@ z4JI1kv@?2!iN+7&1pROb31Omv)t}p&g2393jGmbqI|F_-7bE8%AUPXDb5j5yZE5Xn z>Iec$TN^r?N}3wmncP7YOl{4bEkF<+E?&T=Woh!m!wI-$O)bqWoPm%#kGQ3?ld`F! zgq@AOovo>@Gl(AmO4wQ3IjY$k8UqBHfQj}St?rroabo`Dd>HU2 z-yhl83N+d@!l>@dV(u`-A)xqpC#gPUewp~wPrx@W+6g?c=Fw;>kG*gdyRCGLoTa6zv zj%BF&G~ePq3ps7@uBG)D`nf-T#hG8rlGaX|v+2IXnNyGCv|@u$4*IhapW)Q4rvBtj zCo#;ABc%9G_;fC;G3#6zdCU7}MOmZJ%RL*A9=|j9tHQ1uZ+zcmZk1c_Np$)3F34o3 zbBm2^R?Ey~@f)H?`_sdel&6Q#5f`>DqR(IHJ0pC;*hfV6{^+<>!}SbPtiY+kU>Esr z?$Q253G7Z9hxE4FBb6BG@&42RYwT9JSKrewSaow1DO#X0ovYZEPZSpQy5KKR3^5FX zeI1l3cIebZqbRs!^VGj(i8`?QELWCIC#8h?aWaBsTbSwDY?3AFS$RaEAX~$noso{W ztLbBL;a!?nSP>QH85k8<)76r4kTDwl-Hhzc59Z!r`9uPI_T&22ZGm#Hsf(K518F0= z9|laua-oLkT-(>w9}9#^jG20erPHlH#ko)LWGbaZPW1p0u-LIC#CBuk?Gu$>e5`Z% z9MO@WEY(ZLM;3Bb6V4@4B^qr+m;fHYW@gaWblBL0(^)b>FIx4ynl4uld072~P z6ShLTru=7#I(h_Z5W}9AWl74=H4qIp;P4KMS!?TS8YLqbS_FhX-T=QQ1V1Nzh!e~k zT4H(&r+XBilJ{GXK@g~^oIUvHu@4)&szQ@(JSnXwu8I-uy#Jn*CQymlZ~7wRT0)ts z<7~vvX;^jP*++Cy&e$;FRNl~aiBgMAhEbJyQ`CUu2K`HP@r-g~K|5VBA6v0>V(HAp zCfQLT%Ca~vE4|P3&AriCA&vz3wu-}&A=x8wuK@BjlaaRZc+$2;PXwSYcobtF#z9Z= z8Sp7QTHk+tnlIg~ozNYjp-_$&Qg}(;lH2f*JbuOL)PIh}K{kB4pO^lf`82KxR#=Wg zmO|@$PAW+3o>V@WAe|4?1$qV7>D47#LmXzi6?@q~*}sPE$3Ljq;QD4|r~#)td8&8} zy(1;&C2=6QDprTyoF;#bNLPlG!y>Isu0e^Yk!cvoY|Iq>@rGS|MFJv6c&(p~^Q?dJ z)uYZ2%4`;h^hkK@R$0sJcg>f(C3i!D!=hWhwPqKmVFM>DuQ~(er10`e>ioFiWxhmB zzw`n!&W7C4NyOf|jJ#BcNkUZP1qLCd2kwPAFt+L{oxv5Y}YFX?w?_*68(E(YowzoqL^?%WDeW6a&BvM(`flwpc8 zm6^p*KhDkxq| zP7>OtaVppD%ghU`zb-bUf7DddUzp_;rzq#y5WQ}W&T2lMq{y(p7GeExuGWQ3L|0oN zq0$;^$*6ClHlAH!d^Ky7+H+!hDV0B8Lml2Px^f7Y_LWoci2m7>O{ftxmcLoF=s~2C zS6I3WS$--89Tr#==oASlu&|_b z$X)ciSilzp1sUZIpV6#F*P%{uzcp^ z?BeR?{@f!VFeo@AG%P$mAu%aAB{eNQzo77CQE|zu(wf@3`i3`+Z<{(gySjUN`}#kO zPfUKCnx2`RTV7dRTi@8++TJ-jJ~=%*zqq{mddC+m4E!%_{m$7>eBlCo!2xxEfPBXn zESwu~!{Z_#QbUmN#8i+C9q?(OekcUuae3A4sI*+FhlECsV`xNl+{^SwcdY&3>>p#y z|G&lA?~MJ0uQ?bDcvztF;BjF@U~azsi}oM?s{P3S#Q(cCIN01UDzZ4RpHE)?bFVY_ zXJ3EtAB)c%FIt0rkfS1sou5veJ-G)Q2uo7PMB+2boC91yVDtiw8E(lqXE^=v zU<e1WBEiJH?8zM%z<4g#^^b)Jl&qpnm+QrbA2AhVWLp#~ zKbA>tJo&FS2>md%`=i<2{|UFW@{OqQ6+zu!opt-ib8r72a#Q|&3oWEnn$0h2Te&dy zKMuIQJLLNRiyJr<=bte|`x{(ktp89MhMKWWAEw|h)fBxSK3`4Yc;9m}Q3dEyfA3ZX z)MK2^+?^o{Ov^D)5(+vH!PE2+<$iEZs!q6OaJfe6WniPet~3N1y*#JlG0O%;=)5 z|Im)}KSE!E|C_?rKNoUBIRAUg#XpyF^54lae-(0a-7OJ+T`zL+{0qxPK*l-Njuya4 zASL$wdolY<5H(|!^JfQYErt9q!!9jV*R3KKJf{u=^F53rRYgzcYg}%c41^CGF9uX@ zLiCC1!kOKgRwRj*(9F|m}hC&(&E{$ZUGZRgP(Fe&;9BT1G30zA; zH3k3ymxdx-wk#EThk&!KEcv)QF$_QmlntR7XDL#+Ly#0Ch|`3a-XTzr|C00-RUGj~ z{-w*Gtji&ge`iVaKp=l9>K`KHf1f-#nL-W)e)BfvVD>rW^OPD&xZ?#=avCV`@+-^V z2$KO>1orU=h74(o$$pp6C5@wVZKFoKc&7 zMFs8@Sru!w=L+UC^-^F9A5(wiQ~wPu2KN`T5?$8|MI%|`%&iK>5l>$eXAXXNMt-7m z^!>`e1ic=+_`d(HrC!ZCsY~AZva<{zyq0eeD&UxWka>VoS?ti?WL`J?dIr+Q5jxIq zuy}IFl%`?m^91Lm!~3SqWff>y$G0ZX!_^nog5ezK?QesOAJhseti}WRd>fGmyGMpj zi&-C4=tH$n<*lFJ8iGmLzS1`ceSe*TL3H!^gMGWhf?z z{XAlBnN$C^kK2ejFpF?De(`SM6d}>(#U%A@=*N+R5k>^^$vdxq_UZc%(uxtNZ!mHN zi;IG*Uf@ai>sF(Pq(KPafD98!@D{?QF8d?%a7G6{2jgdI{z>qU>T&(46R$(kP5&4><;7NobpI;iQeI5*mh|TPJfllXSoiALwRh zt;Izff4p)`49U)+oj3Bbf}t;gwVS zniER7xNE7OjS>Opr;5HpE1M;<*mCObbO9u__Tu2n0PvP`9@xA|G!h3q>_6))bM;~{ zI6<11!gQ;dAKh$%VjUhzrgU%CIWg~Kg)LRlEfMU{U@vZ;I88xAWUMTxi>}cbRuqxlUC^I-VEbaUM{y-t zX-&t1T&a|N5lyTcndgKDa9)Nx*RW%W_5^3-FV=JmjHC4Gp#I;(Hh-|<(T?0S0@cJmTgo05$M{oZ)Wbq z$A!AqE;iJvgxw_rq%6|8YhVXn;iZWfd!`0a&pccu=YH58{tU{)Z5e)`_q2?nFgC5h z7-uTB;W9lfMvvmh`SvR0S_*u*xQ+OEWrBJ|O{ST@TNZifgOK@$ly6u3L^*;h-OSR0 z7MdFZYOQM{z15eKxL_s7xL7T8B<+}f)_D>T9y_@)+Shfzn|9bj^b~7rB=+)WOWva= z3pW!+{;(N8Xm}ovDOH;dzwRWr!Rgr_oUb$Dcl?&l>8|Q~4W*NNlsEAh?HQjJ&y3+> zan*1K3#Rb<-S5BO9UX0-8ut=1`L+vnL5|XeTCA6AZqjl@Q`)D2H-K&Z0 zipX!)kY=f=AOwmL3$E|;jL~t%30CcOx(U6fNns*~-6q8jvbFle38`05$ApwG5)jR z3Hm3)lkzXBO=UyJJDnsKfT0SEI;y5lb}o*_rcNO4Um+59w$1>C6A1Dfj-siFrJ=Z; zyDosi3F3u7IXFRl0(ZYpfD|Mg?d<<=?`zty~g{#r>|@mM~F>@999*`e=}3M>)#XOruxiE%c;)VSt*QGWb=V##$0Sf?cN z$21wfsZKualoxyR*{XXXlBy;neW*y;A=*+tXByR%iwvpvBsmKvea!DE`UdLzkDNcU z`)u!zI?mmx4yAi}6Txa7D0WTw^=hB&TI{0g)9SZkjkdHFZ}|CkVyXCtgM|4C#swtl zHlJFbyUEAGj*;$hUmnh@)o0P3PKylPE?b}m@Ze_8z4%HKKYb_^*Os@=vha{=Q2jL_ zwj{ySsZrTuGlT{&SRvBkn(|~V`^xc>4OxgazeEJ4hn6kMy@Sz5_3nykH~yWNc~P$s zGt)Fv8#)lo;m%!JrvrnjvYYcGJJglC=nQyCjV9xLDzx4sVZJNHSW2taV%lC%{pNY; zo>H)klGHiouK6ih>S^M4(t`QpyuL5P3!D>6%Blu(3f}jMwH?OtpD7n>wt*P9r%HRe zF?d4*=m;63g4w5PK4vkboypWvnQC?lSLp8Z(=diNucNPnLsf%KJ5_GTkbO!pWrF)r zF+|=f;k~=M@*MKGH0%@R!I@9@!$#mGwdpV3WK~&EAYshp6bmCRCgAt8P*X5!m&7lc z_^9T>f8e&|TCcaiY2^tUzIfhP^}zSWrCzua>y{`zpchj$P(tsOS&Wf0pGRua(o{zO zYWcTqhqYZMPDvCxF25k&sA2avSIzo2FugWzWR2|+JM%>QqdvFb%k5WO`4IS-aoNjR z3o6%$XRYUtAOtlyp|dlI`}F0>QqQj`VJZBTjSAyop^X^o?cVsQX~}UhS5E{6$M$fO zR_FUqTf^pI@OnFnc&Q=F?C6Rn(~jT;I1DuCBlx`!^V8OdwStb*(;f+5z5Em`R*oK=S~Sy#;n0|P{1u~9Kph(iL3FvV zkiVG3744+|(l9wDqDF}edwp+gh_iy~DQ4?Y7>_8N{j*y)_!mA{?@W=*)pKmO1*ATC zd#8UHtFk{L^39pI;+s=PZbW=q=zyg0@e@S>B#$NDQbZ`as20_~q-CJEmi*?$Ee7m2 zWd-HvTt^t5f+GvT>NfZL=);hZQ=0m~`=c=AoE?d34NTNFg2rfEvgY;y%nJsn&)JQi zl=Shc>+K>Y+F)LZiB~Qww9v?9QRJ38eA|2Bq$e-&Vq$MCzlogxAng8V3-N)6o~~F! zjU|t_2k|oM4d}LWLTzrE$T7~(E*EU+5%h(1b)iYi07_)gvcX{= z7&n z#oL&7t0RQuNDZ?+5%&5;eIkjqFE}bOHqyWPQ=M3*eO~C^@_)|&$>H0NEH-cp4(rt* zC3rXVEa#NFo?`GU+jY`IXh;!hUk$0ZF$c-kC{)_XgzJgyfQ8*)M;Dc5%M+L1cU zq!HE4sYz6fxVpLBNz*}51-LyD!ox^3zU46=dt%DjtV&$FiB5(#K`?nDfn>2~*|La< zP8$*B@39A-&|HctKf(8k_F}ozi_2cxo!utzsvKm#+!1K1j2+DuwM9@L>lk?36MkIj zll?eG^Uc$iNf#}!ON%k-+o$!yJnH)&tuKNQ`ecE+VMhJ|lPVwC(pG+8!#4_ysj&ZD8znF5HjFmpcBSbbB9n(e_e z?;X^^usw@4;Uale=FqiYjcV+vfiHCStl?T*s}LBVAukU+!qb*T^F+F{I6z6!8RlIY4l z(kq-IvU$N{B$v9f5>7n90`JC5vHd#SZeXYXi#q`*=;??Csn-kz2h!_EScu@voTXXd zG98JfUo$0J?ar)BM@3ZPm9qb*2=wrmSSAlTPSw~q<*7KQ&*d!c*~fkE-hd!cThiDC zjjyH_IE-b^!A_=x2E0_CVt1n@74y_raGs!ezt=9iW}DdK&%X`(*~;!8$IUE8x^UJag5o*VKH*=Rjvyi zTy&%*lOx@MYlMZKldPIvLw?n2U#oar+OhKrd3_u#R|e6s#?<^=uOZaY<^8P-&Noh1 zBLz>+84sB(&NlO);~ArMdkkW4oDHtTOb3T9vXiul3@_&Mj71NG9ZNa{KVDpYlHB*=%#E_@hrFFv-%|_KiznWU~(5Qdw#x@LI-f8N9;4KB4KScHv zB=`DId=-=M=xwK8xhG{*+PcyJ2`Z0Nb+)b`LU7Z;vwZzW6xV1UPhWxI%!E4W^5;nA z4Z_bByd~e3h4~1(>{+QN6t`k%x4X!DG_*Ujs~kN0P#b2fUyv?zX8JzZFzehpVp}l( z+eiMDuIefk#q6&%OSWcmB=bg1(M-NP;ruRptm+fR=N3=zSI>p?J>d8CJOXIZ`vwVlm zNNC8UFnmh#UPHIuV$?g5l0bc-Q9UkFL_HEB85;f! zUZ3s?U0twG##${m%I(cXEj9%+As0RiT?0L*<#))M7#)m0ICypH_JOphgwL>%)A4zOoo(7I3MMKV6?ggozVMKXT z9W{e8Z5oC##*q1dwdzY_|9S*^t}*x0hpfq6U2V$NA&$C%;)3EN4h->q8qyiBU3&FR zBTSo0RS#{CV%#i0X4Del$)Bzu-%5qQnDT3vO%*f3izGKEffqu>_fIp+9ieFCP@q3} z?*gIzj5TDlB&ZjICmI&KyNvnKMGu>$GZF7>Q*=waO6Iv+V)!xjnCqEm`6ro48$xW= zu?ETp&0UYiJd67eC67RaHiA-Cb=w3PVGRN51eoXF_Iuvg=8~Z{1V}1>3cy^Eac8rg#_W?!}*uVq@d=3mRJ`P@-YVe(eiEdR4pF zCy2@o+N91VM;Y!Z#^6Yco;Gjez~!6S;?c#%g%(7vCLXb5e1i;`&8|4udrm$pmw$Jy zulwbw`E01AA$FCUpR!-Epa5TOSEZSF!xLHN1sLLv6SNd+;nrT|le0DvLeY|5Uwyt; zMMcqM>z!55sdc!&u{C-z{M>mIvZra>JC=v(e28(W^)jf1hkx(^BDm(6%J8_9y?RIS z+Scn&Las^UPu`d}BzD}JYK&`TX`^(})XOQM{SFjH#fsQwdgp*4*O+T;_=k4|bx#Km zVBT;DcrM$=F2vjH9?dlCjvM1+3r2WbjpHxC%IQB|d$H7$q%8JyaC!^>JeWPP@`1x~ zWgjDRVs?|pl2TsY2-)&3fz_9VNr}pdLw7p9=$O%ZN@^;fC&*+trXbPW*pr5hee(#F zq!F2`l(dQ`QuoRW5U7L-B_m|r7S=bG4>}H#()zv}>{|1FD1OeX`=BJHuGx;_X22Wk z?N!p(1^o+~#ZB^0Z?DPu3{)?!7b2Wh;lzedbXo_&127(+j6R;e3z)u-VOc})zO=!W z^Sz`p7pKuzGTV5#o$z>EB=(7C3I}n8%52;SDTE}n)b0-r2QOm?0_qIpWSduzjA@u$ z!wq^S(_6yBL+hCr;b(Rw{2*7>=}{-nI!*bcJ#2Um@R=zQrkd-fQZz)h+Hx3*VRrVa zIH-&}<&_Sz=%kNA7b7&J#d{2c@FEIoN^82CGo#ctKAk&n3OUb~;K^o$POenVDeS$pINN!ZF_XT@`15<06ciJ)~0gC?>;NmNIzB(-ZF zdq1buL&^SsGHJ6$wJhv^XZ94FsfF zEdF$#sz|6yib)Jz;JZ;^y}MET4%nnEh698G&q33H-f;4jH8(hd$K$pVd&!QA__|PW z%k&hD*V?Atjd}<<{!(ZQFGrl_UbrUR_oNrU^q_~sJbf5=0^CPu4-t+}AC~t`upXTX zo$eVSZNH*ZIPp2%6Zbxq+zH;AMfBL7MO;)IImqlkp;vfxsaBEktVO4`8=?=xm8aqA zW|f>$D2Jrs8L>QyP9|IZR54*AzLW5{@;RU1I~1_ryIg8GH-`ssZjh&NZekDMXr&&& z6>L98S?I>VYa~0pkFpT-6(w>K6Sool6t5AzDdYX3fG!-Xyn)m~V`3!i`dfdM*HPgJ z7Ym=b3$il^ax9$APjp&hO6H%ry#~*bM@?&H6dVQDROqnGQ|u3XI55dT4;+%zEmK$j zM0;NaZJGOMg0}NbbKWoMYdg!erTs#WB^9I*f4<4&`@8Soo^cA;_VWf( zW?aNN&vyk#)Yewb%#sy6hISqFAalNmFEBSUAk%&&} z75OHjUbWr&<)$VA;`sQ&Oyo$27$Lzq zn-MB@G~$lh`W_YxXX&_-I&q3$n=nF>0~{ibDW3C#K!^^Br!-F4G5Zx3k54UFXf1$q z$&XdUlg5K39S=4l{JV?j)1b={E&j45lAOt4cR3bJy`suxD_TNQ?fUQy|4W1@zC6p^ z2eTn)nV0G!(EdZ=7^2b#PgetRr7bf)##-EeBvWRT7QJ1Y=b;0$Sx}fzRqn44XG*O@ zVoZk>ESFlnTNJ^>iBKw0SQ7uSrre|X_2<-hNgWBt4SRj761?5GM@o za}`-5hxviFFYKN+Rw!^)(2j@msQX;f2=Mo!oII{dc34FtX`kaM1ey>PXgt;{cNBL( zWlo?D`#hF%R0?{N_+Hk~!a#N)vY?0$6wKcyS73n7z%93@lhmfmejQ^;V69be1N-E# zct4)aYsqg)!cZm5B#!Quz3lYZttNkepi&iMnD8qHU6M%zn`i}{I#MZP75U3L9UZiD zadBQim%9G#==_}KNsZi9v|<&7yKc5Z#P@?(O?e^)a^t-w?IF(|d}TAfq9 zLYdQ@lB?EnQJOAod7qP3a6nPZ--Cuv%@;Gq+YD9bJ#8esK;7N0+Jxst+^jNn-gCk) z-#6B!ev@k`=CtX@$Eclu;f9#UGfK_S=v-I$<`PxD#3`EH)T=6TW|2?}$r$zXzAJy& zt9Ba3Xy%fA7V6RsLW1?#c&N0v7ixtj7o7j?2j(gc%~@n|@9W@T%42=_EO!xu+N77r zI?@m>$Wer-K@jOf-rf(k$(YU!~2X<_77W0JmI%eud>T7q}wTY zYmc_l?~5~5Qr`Gv#eC>B(Sc*++)H7v@V&Y(J)40qf5X8MT@MOFP^W`De2*=#g$kSS zB`8iwtONFeY8RhJ%_Qzbc*%!KKNGGQf%sRYqpe3F)iEe=ytUMjS3I+d-H{Pn(aJM2 zh?#;@*izm{+HM}ZiA#o-}27fR8OMqHyWuw4Cb z7~8koXCK}7ME-ssOV7**akJ{&@Ngnvq9sdW6k9|nayRGot;)BPM=RS#-}cUW-v}wK zAM|csja9wtPI7O#c62^J4;2w;78LY#J-YROI-oPCDq;=V_iEEw=2|ZlA;q9Dj3eXuJ6KQM=jm`-8UR`H12xp(dZV*BfDDjH3ya zpAV;vz6NAdwaqHVM9755aA4;WrJji~KTJr-YMB`5q{<;!rNK78P7%oxBFhn3`P!dh z)z^P8vHw;e73_0Med~U5GL`OJXV+7mh zc5`UuQUolj$@k|=?LXU;_RlZ1@Ae4XX>$JkOYJ}GKKP54hMVgjS!sL6jyyzwVDx-} zKfw~HOtDUAu=F7OzNBbsyCUZNgh1K>{GiItXq*(uJcz!oZV0U3%3$zfut4tG#h3sU>sIMj~DGFbH<8#=!gCuHVY+k3-QBDefHfWdV< z@Pbzk-Cu#U0LDR#Fxb#p*RZ<&?agePw-BsQCgvC4(s(0OOO1vwE4p4YyC9*CfdlPQ z&YH|*7XP4IIi8;0BU2xismEmCDJ`@*Qq%(J;7!~HKI0b=j~cDq$ z?vLuRKhxgH*_zqi#c{MVaWMwACNe#8G&Ht01!*fPfRtT~tSy}^04NU!F9($Qua#|g zTD(6K=r{q%$y=H@fpjs^Aa_{;jRr~!`+Ym;9rVsLN8HfK^am?|4V1O3sk5cAA-lMpwFwX;WeaQ$wX`({ z0j5&6Vzy3}zX5*A?v9~EGL0*&>r@VNMZoPUNvq5OY?2j%Ae8yGM2 z=W+qc`ESYq!o|nM{ZqNz!36%0p0lH&rM0OeCfbjE(Q1|+ra!6^tZHWmY-j$Zi-PZ- zJ)mEo5MVh;kggDfhuc^H!pm*KY0S@U#%;vO58*NuFf=oQ8goH-jkzBD*BC(SyX%im Z&W4W8zqBNPHf|oEyV282J(9*m`(Fq!aK``u literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/FAGC961208BXA-tax-certificate-1767252858.pdf b/CSF_ejemplos/FAGC961208BXA-tax-certificate-1767252858.pdf new file mode 100644 index 0000000000000000000000000000000000000000..01881c6a4bdfa9fe1226b82b7c8aa7828f31fc6b GIT binary patch literal 139896 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 z zXmXXnh$$FC^37G>AM{S6_5*WJ&Jnj2`}*{9V9d|NVCP7IQrsDE#PQf!&-_*{j07|G z7bZt{w71htU4}EePe(4;LTE&qV3iOTbFwY zt-V4RIw$Wa3iR(~u~Y&jF#GD)%MQoY^0^A5QABG`zT~j{ z6}uEcXKS+8mDBbo!mF5g%mJ&WIK8>7?4b|{OjuWybY=Of)2Xd;SYt`h(^F9$6GKCg zzSuq6J+Ft9b@GjBv6h;CVg1W?x07(NbAEMjiC6{ZgI%4v|H$#xW5Lddq^&*o+QKC+ zF;#fQSW$mR>J^7?U$RRYq4I|+eXrize>AFc5GI$hYGcl>us`T97VPjv^|dK(-+X^E zxmgM$#00&)l~vKvgE)|T4A`j)TJ^$)(^owk`?*HBLR?kow`7w`V2aPRw7Bbg<7@Co z`e1&@YR5BnlYt%2y#6++5tC3l3$A~;$T_NF7?$i54>0S>gM$ZfjBK7XEnI%~VEF}< zb@1TwLwa(qYq|J^t)q;WHC<($qM?asv;v9I!Jps8Q6*RSTu8)dsOnk zh{!19(h}lsuUc%M+7fx{PK5V0Qaip}d*iOWyY#ps?{7^*^h%ug7SU@&oc^NXghQmPzY&)VF~~{3K>(*?_zco$+(RQ!2kvsyAYFC#+-tJ z*W7`BWQN5-Qi7RQL|B(I5qmac-u@SC05ZmHa}$XbK0u>rnCJAbuMbY2N5rr>O^2xA z0IZ%luRN1PI3U0eh=9%sIeGoZ|N7WrI%sAE8rc8$r-wiMCetTU#t})m;=@)ez;&IW zJIN|DAPUSI!NP2URH)IQ)RFTm+;=$b{;!YrPyZCw>zO&k0UhyZJ*A>*^g~mJ0oClx> z&JqBc&AYPQL9fzcsJUbhZm^4)yd-zO{V&?s!?-~LXw}-M#nBC(q%(D0;B#xio<|W1?v8=mbG8BB9!nG` zwI~6+jqpY4kWjJE4q&D5XKaHBAJCNb;h;m%+eDt7;3?}Op0y2mW?j&?u5la%d_GT- z)Q=7h(gE0+7+F9M&KrvsE!e(&%dXwqf7rDCz4s<*ITlvDJ*U3D4lHerM!jw8mdAjX z*I#>?OeQ~=I1C%%avU8Uo``q8`toy;NI1$?IJO51*s+L3CKI(-n7(g=M?!Ta=b;z- zs=ln$xS%6Jt%yNRNji6O{Wo8KyKU#~$Xe+DrR3iI6Dv0^x3*rga*6ZJTeWhId{WQO z(IP}599*k zS#iHaX?z;611W8K3p_pcu3ENm?TS<0r4nXaZD^R=<{hi7t-oAiv*XmoyumJk;UP9Q zotW2^n;YcpvdQ}M&#bN2+dBr8lj!X2o8svk$WnNyV*8K3Dwe{i^i|pMlXLVCW zYOV3fV26WfbmG45;@fwRu2}c2we`}~D_!rVlGT!t#?-L$JAe4r`s?Li*qyt2W#h)% zX=A~TQ77V7g~c4%u=-#+AIVz0TRPu3-_Enw90} z=tQ*N;OHD)T{`%1sZ@hP&L)#n&tKTP;M+yk)+@H`c8e+M&>*NrQ0p6ZcGaeDKeb-C z>hm4mKBpF(i7H5M8|-O_JmIi*!55!dZ*sCbvES9>(pa#=GpJO|hF1T*_FsLu2>z3>vnkBU;oGkluG)VnG^L(L0eJ1Y)6!K-tgXLYzxs4wV2ehf8}k@N z4MxofJ;Tjo`*+L1^ekPm|Ds=}-}dV%Wyu{%1*fSw1lT{prHxwm+Va&Fbch_)R&nZa;2b4lZD`@504Po44GK9}9Lw zoP1J{o&8#HYd^QPUbn?5E;}|nAvJ7!=(s>Xi~L-HNzDk3YYZ~AjKXXQ;?axMGJw8i zI5RMUAZoRoq1Q2+3fv{bB}!qve7Hy=Xk&JzG~`^?s(Uob&bE^4E#-mjW!^1?SMq{3 zvMJuxi95?<_7w!Lrw3j+V!7 z&ko)s=Ts6_PDz51KSZl*Ne?X)v$&o z?ld9l^ANc{20c=jU3}m59BApW@5oKC?LFdQcP!Z}RK^vLCiviF`UVl5ODV0d$uFxX zRSb}+8j+I7pF#9UYh^=CK^eZ4a_CH0?*<#q(ua`50IhqYUZoS!IFz#bn!>W$g7VIa zMhS~&fCC~DLCg*(oMO1MzO^7MAv++lGC8LxGC9jXsye$^F(Nh*o|)-%QQXaAQ0TZF z^xFK=?4anxo4&chv5f`g!oDFy11qzb`X~NCFs)LPAD$R;&OP|-jo>pkLeAVsboXs9 zt;5VzXr>i_8F~FfG45WVLFk$5!52J=<1+;G;ZaU8BT*m~_kRIAC1vs(NEIpfLqbpA zh`e;CJUM5uwMVDW4)?RF3(HLNQ&_GN$q8aIu(}L*ESyH7qSaHtxwZLal(GgXgAb$i z@E9v@{}7mP;@sx)I!yO#TC+b7?BEpB0KKF8Z)80O5XT1S&6xr}!=~Cs!Wg#oq2t5P zrV#F)^#IO<{C~FoK^YB*>3$mk2)`O$iu-FKrmTwuEpglga81}Vwi86e;hjWGT^I1| zx&WX+Kx(xCdlVOpiW%%oj4#X)YfoE$=gm2Y=(uvJ7O-Oxi#$g38ckPT9%W#J_ecp2 zHJjewQ{KV_&lUMFxh5kvIw~o-hBAbLXL}c=B0f3F-#ZvP8i}4SQ49ct^H&>7-%&w7#wcXS@ z8j}W7hfo+$MXgFoA@s?sYZmAgdKhvy(oLJ;Jn)9H2QUR%p!yaFWQZLi;8f$VwqC)-sV^T9~yEt;(9#Hi4)~BVz z>5UD_XzOaqY3ygyB^sG z+}}ScIlH1Hi;9vh?{}vRZ><}NkU{~YL7G z)mPHpl-5{yt+mXnzcs$I@_tWkq(;T(vfDYde7U5(qtaK-uj{VxNW8tMGP*4)qmRWn2?*$UQBIZKc<{>m$^1CWdMH8k38HR#wGqsE*!L7`8<& z9>nS3h;k+9D4rl9f?yOUUggD9L4CBEy1{75={Fh)XN_?bSP+p!L}SE!BJ!Ys2?2L7 z6fv_4W2WLY7?Jb?x`qifM4}C0_nSN@sJT#xsoDwwwTRM^=A))6k_cwTMkdD-aU3tQ zf|)6Xji%D0ri!7cxpRW1+Ej>fG(OMFbUZ4iqAbK-)LgdHJXe^hD$K)R$7q5`59S0l z^?;7?sS#KQGgmJ)7x^KS!pu{Knfo)Hj^7iav?ie*(FtmriwE;*Mpk%K({Lr9%f zKXk{eewb;$H!J_5Rx1b6@29r+4@-ZeEBuBc8)0-L-%F!OmE(PG z7Wo|r?tC!9+8~_%n)rKPHKaPWlwQs8Unl2vAqEACqB;$qQXDcwNv2lDD)~LAZm=rv zOb;cwhaBBm8%fS}tIoPrpLe@F<7{`87ptpSqvA3;tNMB>mBL;|TSimiIcoV$N{Jh@ zEwryGQ^u)j$vtmSb8seJJ!;@prk-m}+fg02qb7bUtE&edsJ1PUR`D_wE zC=(K|Jr<#aO~E;(pIsN|n_6eUFb#$syO%MQpd)y?uEzBobUL8OU~&|W$z{WJ9U5ZV zu3h!eZ`{dI?8$ zf@sO$=631L$GMYUwOzYLA(xMRdGQ#qV<{?X5sO&FA{McTMSi-N2$Uh85~39i*QWWb zqSs%mNH|O>_7HMAF$}?TQ*?SAr+R5oJQA(-g z+#5VjyH3%=Xisjh@hXlx&|G+%T79#l!MmlvB<9|KNsv^5}p2~&3;p1+}Z?HbsoEg;mJENP;K|s6phg+ z(HfQ=Ny77Vf_@&@0o%G0u?C`$lsUL@jG!o@XAaWy^K!z&L$A7BS-WP{zutZ4m4E#G z^;chhu=5hpRdh%{!Gi7O{v$EMk$LCZ+5y}Kr0keb|46(Hp{XkTN3m$!xe zLa?*)P?Uin%P`S(JR<1mhN1?&UaQrr#bRN9e;*UrS&@Pgcq`iMJ!?wi&*4$EGm_h z-kP7^N|%XLPi}3I-#}1PBpa-)VqnvoE7Q)FMQ&^@y4Y72*j^Eis-?if8ez1yRs@KK z>U-NVnkplu{7!>Gt`IbIwIs_$)Si}5c28PG>WQX`fRck<- zK!p5V+d`!<{mB${2NE1%QBbWP4<>!GN zprA;npR(!>?xP8wx^6=5wI^?Dk3|w-=dE2tRh^)p=p=fut#XM#jCh?{f&<`*10Q!^ z#@~_0&%xNVpkF&r4zuLmS;QhgOTZUX(pOs8)JPo|mk6zq4EK}nly(a_zfA#lgHkFa z=V$nOdEdNw)6dT@r=XM}(HI^)AaqJGqv&2{U3agkO1&0EXw_i>K{p?jsGQo?k5EnF#n|Kz>o zn)q;hxc6?}j7e{1ux0CuKgjIvmW-0)J(XxsLw|K>Sz9MZ z_~gbCUKdy-K8EC(LZGQt{kGqE2K;m)f-VA2J5iPRnk^+zPk}q%el>c?FB|mLnX&` z)!ZAQXUPN&7%DenhCWKJh*>gwu#5@Z04c|0DNG@#XZ z1zKBMi;Ig#_o!5=jEoGCNYvNY2M#?R>=@yzcs&~z;*Jm^lgU6QV5h-g$j{Gb;^oso zV`5?%3(l0)l|G}HH|M|sNiKpBvR&d9~{>x7m zEH%l|08;rNkI@)?;q!kkUcBgQeCIZoOQCH>?E~Z3pi^=N>(hKZT(@mr0~&n4VgJE9 z%~XkYgx^-2dhev;)}>1qg9~rie&l9yb!Jvr(2l>){e0w>4QoJvo_~@OP7AVY}r?vcAxPrZ10ll zM)jz_{r&z`?|<@@S^EX=zxmGA9}fAY)WzO9wP*GB-!5JH<(dsUkKK$f7?SH0tg3)J z*S2li44Zzpbm>0(s}XsvgQ5rh{hkFbzckO7g8WAAFY1TaU!R}t8xD+~pJMnc5M6!5 zo`9T^Zssq#x>=MwW1+K09<6k6vKDv^T<^HNoQ7XU11;fqR8@t?q_T~Y|2J;*|2}!t zoGS=y3+G~a(|cYO#RV+JfBT~}*LMe1>SOCqhKP$8&mZ%DyVwOT=I zs|jc)U2Q85Mo>9`3jLjhLUuWaR?=8}fl_o;C1=1gEr`a5sw+}Ywxl1di`!iqzM(nQ zsWRh=5g;ABYA8<3`~ZVPb05l+UAli(o z>gDB?oSY2y0AR9v_iiJOIP`do&Zv)r&>S4f%E|(WfM^4H9@Phyy~iVpMo$4plai9| z-n~mSendCol+m$_?$6H729suMYrA8|4z*g1;6ijeJUr~`>I&wBP#)|ASn}hKKc>^^ z<>lomDJfuoW@ctrR~OMU_|#}LW4&77+_&C(YyJB5;J3T7vXZ!TbaZr4Q4ujfa0pBS zG5Sbc7h(pP75*KU(5URGr|vU;8d# zfF~dSf~zj>ZlsTUG5evN^zOzKm+ymGM<(I&MaA5%0{1VM9*?OYb0D-*sf-MU5j^}` z^FtDo5(?UYJq|1ZM&rFNTrXa9bc@V%v-2G=3UDt9!fzxO_^v!z#xVyK@#vKNkPATt zUBe;(A_c>Bx1)}H`1!rQD8Fm5sX5g>0)Qv=sxp~ddr;u(@%X5w>( zH5hbCl}4i-Q#4es)#@~wvGaz4$D~H7(rV>0ka_}`R3sL_8!C~jbqIJ` zqXxZBqflv83Yk!$)&jc$m~H0!D^;j<{N@g#BVB#x8Vu@x&e zZQ1ITRIz$*R_~+=Nl_w2QY>OGAi)Oq-b51Yy>}9rL&iP*V}KDyXTDyv*l|rE;zAr8An4Zwn*k zA?vln-31L9wmoe7*4%qd`SuEN|I7rqyKiH}G)Y9v(c710wtdF;r&tr_PkSw}nB*NbD0 zfZ~NNOD`RGm0y!%V`I;qI|mX&C=`OF6%`fWz@I;Vp2OjQ&m12gzjyCmaHnN5*~5nq zLqbCC+_^J7J?-r53@$f=!GIoi`}XZmKKaDm-5uP1e}8`)8yoQUAr6@L_|dF+qVxg0kK1y0g)k--rio?Hqc*?7EEevY;%bV_a!$s7v93%fx3fw zf~+8u_V#wT3M~oE2+9lU3o(?sfIjavr<(I!8wO~W*I#ushe<^adu~yYt&%Sn@%Aj z4~{~$gCPG2p~a59>+x-OLm#=-5n7Wc$M4Ne3*L3W*=hCp)FDdjhy)Y)IX;^Yy!FOU zSFQSR%h3n1B@+ZFkddswg5xu@lG4Ju3ok3?f2h4_%N+a%va2C z8~sDBezI}Zs()Fv>ht4{tXd9#qKcF8;A6MW-Weq|v~1Ap!2?&Bf0dGR%R3_vc{k1t zwAAt18nG(>@~x0;OR+OLP|`i!JHFsjH+r27OU}w0k}70k zaJ9)}48W7_Nhx7Dy@J?*qQ{HfLQ>-j+83+ZmYZ9ekifC1JfBlj$#V5C$h&^@*KfbG zYSnKxesw3ZeT>lQiP5&a+fIAm`*_u=AN}lC+i!)IkIzXUer+Yo(Z3+b_Si3MLvo5s z+d`SPpKe;U>IbV-M>2dtN-)A{^oyt@B2Ua=M8RQ6Ympq6^IU()zaX#|)2UP#w;24`c zLCRsM2nrNhJo-? zJD}0PfRFc{_Q|E{`y#O;=xeBsaSkXhx^-gp&)!|N>epYKx*6Iw3hcdQa3#IgE@ozC zhB4b?W@cs{^O%{LnVFe+%*^(fnVFfH*}nVzzH?5ha`PiMm8zstQA@Q&y<1OrYe~J9 zv=(~?*7fggXU-ek3e}3Q%PQAqUD%%l8OXvPLm48uQOJE|8-!{z$)^mNbbQRdEQX;G11Bwe!9a8V-^d4}PnYYvVt;kqzCWMy1M;wS zbae6si8#YJ01D*pbHDMJ@&^%;KyG(wM|jOpDG{!YKHs17U1kvS#PadG{l8?sUcK2U zfs#4s;<_9;KAg`FLw&l9eum&|=qr^5GsWrv$+HYwNoFD8K z<=3o=bladlzLJ$l6cZ!vv(a4$`58b8elZJ0W)I8czWZqCyIM&Pv3T6km^S)lW4YYw z{%WgPoQy)2x|oOz-mFSSU*zO;UtKjPM*Pv&iKT;6m6PeV_%_7)K0FaovKLZ*3#T6X zF8|rTH(%etti+V?YvN_MZgjUiKBXif;m#@nt;SK%{PmR=1h|xLm4c6?xTHdwDE=q2 zRecL@|MF^;fiCUQ^5JU<8TcYSr`H{sL1-?%-RDb*o~AW;vvtP-j>ZNzUA%L%3Gd2{ zZAFXK3S94S4QI#^>x8CFDU&k4_DN^%Dh~dM*X2ZSl~ybs?>=S7LZn_^kVj z)g*~(q!HOjkr2zdy&PS{>O|B(m=n-PElWxK&JVj|ZYj*iNfC0_+?4><;OpS3gw$cD zc17M$BpTQ(p3|4TlG^^voGOR1F{f3C{7aY!6+V^porl-gjbeTCl2(h4^A|C5i+h@F z1Y+)a#46tRNZGGzkHj35g;voa3g&TI%#@ooW^V%gU#>rRelYR!l5kUVU3528^ly%s zlo$BcR^;bQ()quqqh%j-9-H6(UdOB99bbMOr={?QaZt`|Q|8^cZ7!Lci@3PJYweMz zhoCSug4Zyjx45p9g$s|6o0^&mB8bDmoerCpsm>%GT#zylU)WMj#X`Yuz~WDes98Qc z=0Fdxo`?zvIQlBTn^C57M+co3?jkFnYCc}ZcaO_XRM}!)(9_jQM#7oYlpsLGOo+=| z9Uy7qbW+DvcCEU|T6z@c2ufvtxHSg-RE>90w-VN%qQai(+3yF%iixS@YEl!ENqXDv z^t~I1-q}0Lim^ZKnveUO!Ng6|!^6c%rZq6~yem_GQpkK8skAP?7HN&2 znT&1JIF~+io`-u`M+fJY(q_gijk4qT;P#{$5jJ9MOm4j5BnRD#Tlyj{)zB{%1YeLK zpb+`)Z3tq#f|<}IbrqAbWzje*F&y#%qD_!uz6{J_wNVlAmW2ymk1jJsy3{y9qufc2 zZr2CnTwy-J9K?$FsO<;|Sz^M_!r2uro{q;fS8Yr7YbO zX?sx-n3r1^8xs>q02B%Vw+q|?%*#p~#iug*gDVnuTn}I#7#oX7Nfn0+R@_V!>iZJv z3q+6KdF%cDG-|{&!{_4SqN}T$hxV6E6U1*GffE)i0N_Z(V+2O5Vc6MQP$}dDUSfSe z3_b3!fI|FZfL?E|I<6BcFmm~VRft>!$WI_PzVLO=u#`KL8z6X6P8m^EpZ ziF$kUVgbzxP|wY;2wYsO)PZh&ovHVL7@F4`nYn$PELKs}u&*@oeEqB~Sq z@wDIJ@?*y8>C_Q%Ke2-1b*I$wP?MmgIGmrKe{EeG>GIl8-rjh*25|!st}OY~h)K}f z+tT~mO#S<}xQ@HEyHBEmq&e{5FI5e>Bps3UMzI<)(oOqOb07276lr4JA1YY!6q z6d90L&(m$p^U@hyC2P6CoSdA>`LX!b{I^bsR5)t%aK^}}iyK81+&1ETFvfgO4^Hid z*KWICfzo_rfd=`K9A&I`qrIkW*mbMPS-TpMAjW}`=|4kg0-1a6K-{5g8p^piX;?c9 zfcPL~{H4Hs!xH1u2ga%ib=>_s<@m_Tnw*_zf3x3W_*90*UcQ!x{Fxi5%t{zS?$V^U zibA~caoFxnu3RFaposN!xsoi#?@#Huk{8wzYkNh9N7lserM>Y&Lm#fzETD6{r24vo zMC4#`Xrqn}iG!h#hB4r+&V@2Vbxp-60(0l{$8|j(8id8|?m0FG_xxSS%eph_Pl6CF z)Qw`n!#kskhI=BuyikjA^{EqPfB7()9V(yeuE|oP ze5#JbT|bp3v0RwD@@t4+Xpln1Luf5CBmLxh4}Ad2fXUKb1#-vPq$nuMf!41PjMy3; z-ihmOH77Ck;_^BkBAciAawNEd*!V~E)L&VihTN-aFr$+5*!+Ph4)&e^=i{CJz9YqC zJg3qCe(jX8PVcD-Eza^R%E!+8m$gs7YO;3(v!ix#rYS;4iBXFzG74&oqJ*#d6j}+YnrR6`di3wVds7HxOd_RBUmHFD2XM?b=WAy zRGld=1RVXbl%oN`gRoy5TYZ-4@OV-u4c zE@zl5uK*NoB>l^^rk$riB%nIbv!l6UX(D}UYU-0$^TzBWb1Nc!feQf+D5N;Vo$tUE z;4UC@K}%9HGW#}d4!D{ukghqpe>+1Y{Bp=S#FBoNR0uLVz#kcmQ)h|LO+w z3@RF$H9%8bT)gLTNz0@Tly3X={+O4Smlzrg;#OHnDKGo|?d=3bu>SLM`>V>G4>T;p z_pQ`7Kntj4h^OB1cnW_+|9C1}seIw5OSA1ZLJKq;fbl@k$lnN> z)s_Ahy|k{&2LCHt$0*;|xWDxz<>yj@e29-dEb~6oG$t4E2A@YVMw>k|IPWylmQZZ* zb&S2fDDG#5Zk37dJ_Iom{9QhJ+@TUj7!XQYj7f-Y7kFBtu+SpN5t)^f#sk?shOYu8 z{-icI6eB%*q58_K|HBx!qvm}C+c?f_^XzOA>`eMhG15Y!r4QZ-p4s{iD`{zDVXjSs z}{yXLRmSh&06XpM`57HCg$8ZShS;}@V7dlLN4->4$Q%rsC9K*@5ixMoer1Y z<=jV1LYocGhw(B&r3-EEgcN0FpzeLR(&^Bs?OH40z}F$uQTgd?UUON)$A%_Mp3Mka za;`~4M=eLx#2;S5Fx;le4%rT*^rR0@t2&7DD2cnCL-@+&Pl(bB-X^vD#z7(?-f6sEeEN$G-Kaf@b{_V3e${7gp{<8@uqa z<0fw-oXQsIWJK-lgaJ~#Tqk%;WUR>SSNZgXd#~Z7^ zuY?kI&jS3aQXAgqb-W1ixLwjZ?bq2fd>@7=o;Wph0xwswqb$D`7j{sIEvUwBxW5;v z=086lvgxO|xR2<^Fm>xPmxHH$O^mI3N2Q;Cz?eFpB@ZCftZcsT-MwI53wbGBAPuNE zXN^D7$6j@%P|Tc^t+$9=OdN3XaJ|2B=W3Gkdw(iwQ^lfi&*(H%lyCmT7@Yd8Y(>03 z7HuHZSljF0*i%Gocm1kO=IT?hkUQ#;vC#hTL^*`R^}((;opJu_WCUlHgWax5SyNo8 zsPwwHiT~3m{1fw{57q=Q$j$}Cavuw}mBhW?mKgLrV%E1S=>!twaQFmG7`~gFv+{o& z=xREw?*yzc{yCqqAbK5so z4PVBcmjg^*V_6^;4>?(RI#P!@RIuEOs6XZ0?~;eID!L{aAI(!U>MtuCvbo0t?u9F1 z)7eiS(ClQ{nv_lso9ZC3PD(`bX^p+ejf%R8}W&8UBBSG!h&f|LB zy79y@?DPj#z!3-mWF4(zqN1V?8OW_t3J8+I!oo&Iq?wZtEl3~M5(oy6y8U`DMmCm~ zxOjL6_pbmcNh3XQwU418$qQl}I@GZ-b`f|;2)4jl_9xDP+51PKakr^o+Lf$Gi12lX@k<#Xu*>7Y-qUioQy zqJ|S!ET+M$O9I++lN{t>imDXMKjA`51w!mB{ReJ^uV(FAa=k%92dcum%5^INFI7q= zp5>k7?5XS#b{T$|WS+HPc;F8BAj~AJ@2>T)Usx<8P7r4DZn!8YAl`^qXi!=w(2b}F z@?abB4-ZZ!TQh4!CIHq;U_by7oBHU$>+Grm+QA!YlwfNhcMSzyJ?Gm)0UvjU5D z08%ZaP+q{zN5GE|87#CL0l_?e@XlZaI7C?OV4Pl1QCJwHWnf@nr!JtApu8GTsvT6I zi9j4d+`fAda6uv?@Gdl=oq32m|8AJqpwQ6y4jdtbn-w9AI2f1!J~%eNa;MC{kYVv_ z8=wU84Cp_Clm>=}g~=80oIJ~rSy&I?e2LLl#Mp^8$&pb12=JTT49r%+$Dbl%)Z#@H z61ukQ1R9!fL~L1d0(Sn>9A^^)f0x=gKPN=4on-dSi-J zYV`)G5g*btQlB-_3)P@Mvo3S#!C7G4Z}~? zCRH6v^6+So43rx}aDhX-p*t88L8)32sNqz1UOaB7zKT3ew3MmdbrSac)Bnv$pvTW3 zUTv~$Sscg;Z-Z-dJmGZXME&X}E?aWlPch-M*jP*t-3G8lA6pm@XpyrnKk<8JoGf4o zjK3ys;%sbC(v2%PNh>k;%o)-{spt>} zq3CvJJ_#bkq|byi=8J`!qU8iyHMPpqcqRwKtoNhqR0J5M1UxtjViDQ(1!m8rbWLy} zX7J$#!J8v^h469e9G=1AzasE`f?XYgI+Xn-^2s)tgi6(DR2iTq|kP33|f$wK<}vi;k79}Fcy439jqxRCk|%V zX5>m+2tHP@f^8v94b$VosLDV#t1Ml~5-=5jG7ZWek=(x*KGz!6m{wqHU6N&XWwN4e z@VFdnQ3n2ti>#*Ra8&$h)S9Z!$6N+8wDb7VcQykbi5vP$+-iRihHv%DqspVR=10GE ze{w)j!*IXDj}fXYh(If)1p5ysh5X```128Xj0cZ!hwkzg%9|2#1MlsF$Tze*T7$Z~ z)gr!;Q=T9xDwL@QL>CjX7uz!Fo;kHMr9=nk%DLjEg{qFZhIs2`jR!lIBClG`%EGzQ zt_k~k{+cxR&L6KL?+xqG0AfBbe;7)LeP)UaPwg;SD>d@SM1+(91F|xkS}sec_X&Y*<_=Hj@;QIM)Ux-rK1F1qO`d>8 z0-LGW(;WHA)uu_2#TR*8V=ZJ0UTM5w{Pk{`ndglF4eAKvfGa$Io9#qEK0$)8a}sc&N{RN62M6V@ zkh((~IcN|D#02I9$?-~5unSI!W@~F32Au#r04t#a1(^)IhdD5EazaUoeA7>?g;ZNy zTpU+bQ$r7B0R<@wHr3;PPj>U&%Nbgk_w(kP81=*_Cnu-BzrT^P7LOO$AI<`k$2VYY z9Z%aM3?d_7(j*Z0;Ru%qm53dw&&I|kZ{#mT+^)B`cU)CN19R(}qoZRS;x9Th7-C2T zBqA+=fpDr5MQSA1I0^~QcH=xc1sD~745&sp$OdBCM)WjzO^81b!9rAd-UR$UaJxiU zjt&lavw0%5Ff!ofhkJV<1~3sJ2S{@4AW0w^{y@Oo`HaMbwWv}-4!k@(e{&WVRA#7< zATto|Ae|v(!A6Ps!bmhP1fW)c;&+GqP4b^MCe?^cL>hXDQOg)3Xx(QArph$0oiU(;RgVq?3H@^F0DKf8Lo({A16 zovF@HMXsD{1M#Bg3VnNnp6D2a%0sFk{gb4;x|fmfIk!o1>&UKs7*ZTRd!t}X4A!s) z9iCNcv51)-=QJF;`%Yeq_s>yCMF93Jva8aY2!<0T>EO23#^(i@K`CQDd-cz*s5j#| zGlnP%zIJb#gQ0A{D3RE(cw{P8yPsAgat`)uhFU*V%`l&GGjmU;96vWTebq#_lfW+0 zN1z|K8+$uUOcc20Z=%p|SkhKYlgv^GR1pZJn&_r>&H2cfr#bPM6u5Sq*}Y)7agQ4@ z))S*s2O!>=Q4&K`EIuOYy<Q@YL|v&0o8BBHop9IGMIOV9I)YN(_T#1lRlZ_9lnyjzr0+^r`_|Z ztJz>Chwy2bM;7ny`JK&DF7^AD#s!u!NdQDk_R=!QTt|q2=a`9Glj8F7_oRlHs=c?q zN!tFFVUDP95ZcwKm8q7bu-ejs^k7Wp{!+^_1g08P5lR6290A^$kJEWu(rD5+d*6>A zXZgj(du{bM=iiRQ`(V;_{rENbI2n8QyIR>4=q2k{Z3L=|Q{IBPLSLVZt%lo85!>%B&m1nrxqlqBE^02Sc!uLsM&j+V^(u#X_hKblEDbnp*b=dW<(9b6;Cn@txCCgtN&yK;k zKzbVUOwrOT@4goZ?$H}9@L!@m%`_PsVSh6ujR~IG;rF9I^?f84LE5CmuX;=vu=Lzk zP=RciU)lkz0XAS?Ty3+PA>tr{9!g3|pk1IZHjecj^gvAbGax*ZFo-qCJAk-`;~1&e z0a~64TmmSL;6aMzW>*mQuihHa%~0=li6~%=AAA`o63ErnRUT2Elim9yhLB7gEI>Q7&VA9K7Dr12(fd*zQxH1qkdCdzyEPt!~m>IO+7JwgcJ{PEfOIe<=@dkSEsD9(Ti#8E&IB7}ls2S_48;t=Ab+mN}P<%`_A(eb_ewgKqU z$)TnY=PElByZGdh#t)aILQwcwcKVCYJ-}Yt812`ws|n}l_wfM){TZx-!mI>8qEPzS zm0udx8{M~^j!w|F9+kyJgO?Mt*=px(8s3Ly*R9vJ;ze3En7)4a=m$>r??<$1YHF5j zwrd`b5%L74HmxVO@ccbxtzTr8cdCielNwiSQ}Bg6XMxXy2I6zsxrEN&XKkBt6^o_0 z1U#0QX*%m0TMM5P!<%0xLyx(_H&fmeSFb~uh~bTq?+<4z1-EsT0>_THmF!;#7pv`` z5%|ox%%y2%51wuxuKaH;gx?3k74nso-JQ0C^DNS4QA6QE>+i_ehl|tMt_4|E<}j--mh3vj>^NVWa2p#VS4k=&tL^$OmvaLs0EG21$ zPa7%l7>DgwFSGpL_pv^ovyeT7O4ht4juZ{H0m>K`yY;@)1Wabx$&0tlse#PnknZEI z2Xj-;Xvf=|FN2#|34*(h_eXA*7ihWC>L4gqJ!{?ik&Cu1UhZc@^HF{N*sMKSr=I(N zb0*c+dp2ASV_NQ;X>|w32V)u=Smy65P?yy7DqFZLeTTnx>|+(A>x9jYjv*po3vE^j z8rEKdlNYzi2U>nQl?Kh`Em09R_NUvs^x*y%F`%>Yc2yVa=E&B`S0 zz1)00r)1UCmO`Jm5aB7Ya~69^>grdBAE+Q?D(lv|k7ukgZC!R^I!?3Z z{N29<%sS-EbR%&S$zvR!lf43g0L`jw_t zL_l|XpN!M0t-IKEdrj}p#$4)xrx=Lyq$C`9UnIf1Kl8tToYb9bB+nS9ZtES$2gk2P zVd8<{zp_?>KZ{kV-ouMw=c}t5b2w%SM;g*UFF<0HYVS5PP%Gn`P;kSE-E|UFC7904 zTRt@%U?Cs!@5KjiL!>l>%&cn+l$Fzaz5RUpp|A4k$(0>{mnv)d1F8#I1TK|6+H0xx zQtUt;A=uYeeq{R+;k8FEq|1}#AxzlHLv=bzMl=Ua^x4kBGT@k-laqHUPaPL-6ZPI_ zR5dpT_Vp`g=i3DTEyMZ6cNPEj^E2uZQ%H#E48k3y%D+1wVl#3&t;8A>GLt|GD4#f9 z;_s`il@&|P{OI&FB{0F72xAQc`FF>nb;Ux>cLGkoRt z<^~{8^SC>J-av+8jKpWax(nsWSE{P2Qt+n7yK?cCK_rza#6$UKdQ zi3&`Wl|^kn5$oJK{S3Fij+%1P_S!ZFxzZ>_Xa;P9P+-6)tu@NIgmxI^{&9F3nFqDt&41AhMKT zG0IUrjdR$Np4Nbi@eFdMJm24ryU!puxp6XLG@5G=lvb4LXNaqvh?$3?qqL&fIcX|= z1}@<~%VVU>Hu+up?oq)5rLCUt^~=ZJpC2xkwwLwraf^-xyG$+tu6emFu0|FX{X2P9 z`R`3nQBf0gn5Ji(w-RllAHZR@Gt`6G>4R0Eya#3o`SH?XQc8b3UQkhlXw$LGN2T1} z7GjgNIJrnvCSf2V@Ao_NTM{;#jML8Ke*aw589u%G!;E$0Y0*J$Lit#lT;~3mjP0-Q zZ|A$OVVsQz4ImdzovwCyrZQ4Ip}N9Rs%itF%$;3<>6I6EWaF_&0^I4p0Q274*>ckrHFbco`>2t)b4)S?fYGFil08?qvhz zrW_UKXCBtLnr$)8soSVBZnvFXV*PP87-v8;AqvgavgRbBA{Tj4L8h>3d@XV)md#Jf zlKS#ZedwItj<2D}vdcDzl7Gp=$%U_llCMJ!cZ8I-HlU>EtcCeS3Gn@VNEE|TPvL%G zY3}P2vJ32eEM6Aye@2+%3G4;B#w>zq9VXKMn+l-_ion^$2~-xFk%6Nb?uP^7o0;dr z1SwCzV;2XplenyiRW53phq+V8$;Y?lunYyojtQpA_42V}E-oT6Lt`f3G=q;a0M)t| zN1P2-4($2?Xsaq(U@B$5Z+m>UdI zxCJN_p{cxtlVpxOCL$vP1CRs66#@C;z@ULXR7h)t)Cp;*UZIg50MV2Rm9}%uQy7U8 z1*U~tL%j%H^G^XAg%(Y7)qqTfG(fuZ*PNjTGJ`SG4NFW+6hZv{%h!+xL07DdCNZpaA2PJ{7A2+K~9PNYWB#Ez!5cg8#= zJDY@y@EMephI}~-^thD`jFIFY<)h*UyQ9Zqzg4-v)88NIJe15kksm<~1@jKW!KUrc z?;g>P$gn9Qrx;smaMjg{W*UQeSTZ@{-9IgxFrQ?lf@fHYQ|Ms`ty^h$k6X+QrhjM1 zGGPL9{#sg?Q*opk*X(!rSgll>{%R2kVecEr=`;E4RtmA7uK!7}dBY$ZG%fs}N? zl=^jjaSRpkfej?LU}TuE9`Aj=RMq0*lfhQ5Q1v36npcxg#~r{cXMGa#F^i1QykN?3 zH>6wuH7I%rjr{AcHFs)$<-W^m6)q%Py-IJ3KANR3$_=i>5_d1Ki%`}jR; z)6XliU^-^cRYh_7nuSOQpYBuL8Dk`{SLTo}-P(_2qeIj5uZ!9j5TUQK-|yd|2DNVb zZ)BNjjMBpvQ1gw1cY|~9w%T{wdzKeuBP~&bb?=a<#&T^5 z>%q6iom1oc08U%3-#As&S}=BdM;-_M$$f)W4`KNHEvW@P8q5NICVAGiCguRFX>)Fi$4*16Qq1K?bd1)s*^6qO#4_Re`mtd464N%4+Pvu z1SP~dRog}tk%);XS>iXjN7T!pio=%m)Z>uQkbrh)sf<~>y!?aMwf6`IZ(4{(wsUp( zConY@Tx*LdX=9+51Nse=q{S(WaNzTIqFs|rhiA)bcExOjc;tmgktFIcAlih z)lY~tn(s%s%G`-n=>+V)??*Mum9MnPMBx84^ms5`d1zjx_3I5{`VuKaA zs>XSbaWoaTsMx@u^ zvd!zs>a9S0z~Bm@wg`8dS_5|uSnB4=kvyVPzf7mcmFw*(PSj!YtF_)+s;h+iZiY__ zQO$$UJXTjeDhkPglTjfx5$CY~58mPlTwW0ev^e4VWs^PaSjUTo1aC`fg0$FbTJ7Na zk&B#j=%`DuTQ$c6IdfsBp^69+%IzBE= zI^q!PGhzUgVpR09=;mg_`%Vhtme%K~cK%VKXbFEzH|=JxktOf385YQyo)Y@%ym z{hG;9D(wxnB>cEUgnYUJRuYQFQdL~R)LvaN;{dBWaSQvm*Cj#3IO2!Wxku2Mq@w)a zUPQKEHQNaO6m%O85|p^}XBeijlEcpE?6xAQZ^0q0+q^xd)3czV+1`ne;-;$7jub<= z0No#bg&~6givLxtoEU(w?dMn&wa`#G;6D~(!t7~hy+wJDZf|V#c00Ol!M9*WE)qJ~_2?m|MC& zJ!LZ!tg;Hj3W>-g-6uM$ib89EC)1K(fK!|P!Ex-Y;aG4yUZlxhFN>3TXfi#lPs6qb zx$VQ_tJ3YqrKO#Up8RQjnXO$u8YNcPI?m5Q#7J!^(TVjF6~T$6&AY}CW*2$;p*lilsxTaawWiM$GAO-)}uG8=eE2Vd)srUO z(s5BL1H~R(Jl13bG*+;AC^F7$ywY;e(bH)^2FPiPRL966weFa_LFUgwJ&I{?s+n=z zS@}|h6Qv_%gw@4nWIJa|&K6o1Rc+pQ(;FY>I2ImujXf-whKXlDsDo0Q5EXdX+7IX#>WZB!cPlau&@MG6+r z$|m63!^^)88qPt+xJzXAq?t-+JD++N%TC6K9dx2*^Ae|?Ago-at9^!8Pa6BmcgOtJ zU3wRVbqoxk*Gd97Qsy5k6)m_qVFkQSlIp+uf*5?ol-BlkqID~B@Xf}b>lzR*kzz)| zlNrOJ{;-55>SKd-r4RtT~6G&^U*H0)P>t|;~@gpPwBG?IqB)>ku;WaSY1gKzry4A zidpyg=k%^@Ll_6vNUyS{rRS&i#8YcfA!UO=ZF*a1KCh8w?tB{o0ZOp3mR+WgN;rZ};uHf+)dPszqVk;$fxa%@gGo|NahcsilgBCZ*%Zd8 zC36&GJWt%4?N^!&cWiL~nTyPykSc$gi#Ib^a%N#h8Dp_X2^tzGgBCKsem8UC7lo;5Grnst2(~XLcRaHqkd)ggi zp*!#Aqoxvy8>Ked4~+>6chTYV{f75v^F+{fypgrY-W#7RWb`N$gjsyc+9OXWZHB+tW;k`(_cX9Jo^2ew$u8MemFnm zbk|m%MdMlNbGy{hcExhe%)Jdp>OY`H&c`XO$O#xZQ)Q$S+L`qbWd# zV?M>iQgBj$4_^-8#7rUO}Si%m^SCPXd`(xw2 z__wo%yy29W!9g_V$%x^VWdi<+(|x+lT)(*KTaSuJrT&xF-a3uNu6?B{q49HT8oA#u zp29oxe{UAeSr-LS@sB*Lm)6A^LpfwEqc zgWSsg)HReJze*ZnyhXO)*MojF77FN=SzO~8p|YZflzc85>=n`HgFDxkxrojA4gH$ngVef>Z9eE453 zss{lu{(LP^|L3dye>LZ8?&TW~dy>J}#>mmh!C2oK9tIG#HL!q(VJBiB`mYEN4?GOL zio2aL5&bWD0}EqACqP=+*}&<)vLvnbO^pE=V&+y(#tuaEBEl*n#)h^=|K!OS+n73; z5wWtfGBE&>>gGm(6eBYO3m_q3Y;J1iM8wGPPl=GZlcR#MgRrf&ovn?rjS~?EAXC`Z z%GN>IPTvr4VG&~&b3$A7u+3m{|>XG2SOKn55f#|UuRI*>3B2r%$J{$CCh z9ALv36cn)83ltm@0uu6{00j*T4Fv@Q1qlfQ4+8@W2XK(k2#D}-2>-gbW4L2#y5``~wIC85k59_uS_fP#TT0Gb2? z3#ib91ZX}eCAu4 z4gt_G7?@btIAr7$lmOj+tZeKYoLs^pqGI9_l2Xbls%q-LG&Bv3j7?0<%q<+9oLyYq z+&uz=fX!XpwAlafFCFDx!C zudJ@^?(H8O9vz>Yp55NvKRiA?zr4Qvg9{i4^uJ;K2igA)7cu}B2;dojLH>ga7{nD2 zK#{?~i5VeK1Qj9m?NNU)`9q-z#pl=cLX$8n-Jlyd%)(%hvh0%G{sZm5ko}(n7Vv)w z*?)ljZ@AWg;6Q-^4-XU>h#%HG@ciw~PB`?|17pL{A$HS(|R5`LRdwCwT-`{)g&axE!3T4r z)G=iqC5nvV+(T7Lc1ED&Q4;@K$V=w^j$A}}7TR5FglqYiK*&H_#VYcSU-|KSf_7LV z>3gbnFn;3LkgR)q9FgF1JhQ2Msn5=`ns2;6zT^uprqu~`EpKfv zrS}oREj3I1YA}T}hbF7dHN}$u3|gHUvv7r`_3iG^Np(uXt;!xSgAa)bHtqATNe^rT z_v{*)YZi*`olirbEO{K|x<2VObjHh51rPR#-X(`8jYt8>xZ{h-G3`a+*&I$J*%V#g zG!|{>w)C_<$y!&J^<1=1jcQ&k-ODXmj;nTeYZ8hoYU}kPVq5xf+)Qq6HD>Vm+dQ2< zw`=&;Tckw8DT&)z@G9Dg!c(u4#+I@Ry0)=Kf3Q9;xVp%kFX$e|D;s07-&$Wep)80vV}hY9@cVyV zpgGoC*ZYEry$O6m2wHm?*G;$|h{Z}FL^P+QYobE?Tr%D$N6E<>8*MeUxllhw9MXH? zA{o|fI+%R{-R%i};1*7fv35AJ7IWZ&SVs<4?bt~L-RjPy+J#$Z45$b#dL>-*pih2& zCHFcqA57QPqc;t!xOUO($!>mK`7A5w0lW0wIm}(d+1lg8m3^_~Dj$u0@bs&>aM=ia zMY23WT;2ad2&wB?$g6)Tzd4fSf<_=zx!GLKsmt}$sC9XZry&$&Ju~R!oI2^xR#y53 zf=m87w)}`?hu?#c)%1$7;opAZy=f+1j~U{8|_y{Rg~1bD!q+=ZeAGR<^lB01}68Ig~q4-@?sYlCKik4!(7jd zTCVg%q&(T|J`;%>l*c3G-AfFMn0_^uXrUv#3XXjpVj$_w}rY_FH@309w2B7QET1f6bB^|Ku&z z&woD$actwZ)e)Biit}Xgk|l=IOi);;OUI;DQaN4UzelBB@@F;yply;nuONa3_4ogi z13;Ort^T|Wjh4jR0=$gPT00nXB%WL~5x8nj#l1ei&5tJz0q7l+a8?U<_f;Ro_n4 zhj85PCR$S5PfK&!^$Yj$+z{pcNn8)9 zx6(qhC7h0~)BKMGhGvoG28KDv>D(&nlW_=K?cK6th`I?Ksa3O`klxpn4+(%(&ZB6# zCyg&ZxE6dNU-nr&dHJyZPI*~Pq=iU@pZ;T$KQqN#?m>-c2l()d?d7mWe4khCTz8T6 z5@PA>Xw#`UQ~7Ijw(;GaNXVcgXKU>=ze}TY(5aO=mws#o?_~J4M)T%k$*v`eM<4ui z>pUpLn`{18Lu<1fX?q~XQ;V`t7RDMdwRE1by77H?SCV66`6x%vk@?p3mTQ8#b#u|W zHLq*k>;!pI69P^MJpX3rB(aZT?KKhsFy-R-C?z&|!ox;;E@6Uyw4d$-u^c1TPF9mk6 z+SFZ*97D(k<`O(K-q<(WPgM(@!gj{mrhkO(E?WI>VW!uF#BIrx>y;_~>jo(Eu9P(nj&sTSY zIfx_Qe@Uo5{~_zWi;sBeL#1P@;gYM|oj6WoZFHqGkOxT&#OQZYM1!*U&=UCJZMcus z*{V_HOlh6h+Bt6nQ*{`cjTMif0=BagA15(Z3%0Wyxb8*1%x|LOE0wd}U+4A>Bo?dQ z-V>=1g1>gRrF)2d1uKn5YMjJ60;h^;n^eez;JOq0b&0<*pTn(lYEotiDmh8pca>u%CJE4}MHv3=t=;j`RPUO!f)6z3F2KPHrR2|Jx zIM+G|Vme|vh?gR523|1cH@4#&9XzOVDV&g*l2&d+&WJ!@|)>+7R_f7afMGq|j=-1Ie$b?3r>BHE{GA=+}Z z(m9Ujfjc2i04>hUto_2X&0|%&e-KX=GGcsyY??6sGJ5i`y(pTRax08q+vD&&BEpKg z-cSNh7pfj*^pbQ7`u2D__^wv{YZIS~6mwxIOe>%of-Wp+SLuwO5Y_*0 ztXU$s`;8WwA1Hp5ioAp<*Q*jt=?Do_&37GDJ5=A_6W9d~i*jhR=5uI#sgS0wk*Dvw z8kl+vIxPVhe2*><{6r#}J(z8laMRq{DA)Jv+r^qo^2OfG_ns)Y-w%qr5L_zm3!3{d z=l2>wHC1#!cSt8xwO2Y<&RsT>6 z5wV*DJk&e7;y|F5i^}xa+oN<`zO4)(p-3pA_Nw&hPl{N&{)_5ctU3M3C*XW`{-LV- znQqE{U>bC*UA|_rPz77L<{_)T@aCW%fGA(+R)DXA{{Jr7nSPq|F^Dd>MS`tzlh<^c z-uhyKs9a=$bWrV(oD?)bUbXX-WZTdgBJJX8l!SQZE7iR;B+oIZse77Y^n+5^P5@V^ zf4r?J7@;^D+@_{iQ;PR#w(U3uJ(wR4Wls3=K?%&Bt@5p?KZ|55z$}P1zur5xTJ_K_ zb-HnskiXTx#(NB6=3)$CL<0>8n$D3IX#rV!iJy6LfB0u{DPwG_ZMRKfxM|A0 z4Ps^+xXO~w+yARkgPQec3X!om`FDDzjp%&v}s>k1lyaT{w(Esk@-(LZ|s}L|@18&tZQ@sThiaXauRkyCh->H@F-j7#uC2Vo#?a)G`DDY}_Z=Q-j(Or~p|8?+K zR!jz$A3wc3(2iDI68MJp1B+;BO^V8cPf0YyF?OF{6V`uz-;^$MgDvyZBhU=nkw{CQ zU~ckr$%r=Dpoa zug&Kd!t9o95`V~=`1tuK8>_ZPdiAZhCO-F<_?D8b$A5G-&ajitT{ZdFRF98k8$b4V zw~9(wb)ee!w~H~2N)Gw3us~CNdx);DgX2%P`nRf?US!GGDp+w6CHvRWxk+-jO|o+Em++mJV^)13w#YvA&1%u)g=-VZEJGnGC|K954`-(1;DKTWEh|2w~e zoqSxLlp3JF;Ea--M`GG1^yIK-62N5hT67!~pP0Di{EhySHb%x0!DO9m@`f-xY3d@C zvsfzG)6Z)Hb5{=}b9sR+>}1{y=thY}xa@&p+KpzJa~4K-6T86usxpRvtfTI2)!s8b zz9;9r!R(DPwD=iJ^!71`7hz&EtPQ~~nX$Tgyvn}b!@Yh}=>AOpX)7L@Sy7QK8;P(1+F0D-TEHGwUmpbyf%N z{}>1@HRGDKEZqN;la_pRIV+_WSRZ1V!==BU{J(z5iU22&EozE~?Z}H84P*VFOZ|Dc z^%pfVZG0tj&Wi?5E`>4k^UFxJ-Tq=x6yIWB9!| z@=f;{3~je!g4#ZQX0YSUbp?LQ5Y;C735sQFgH%VO&VZXLjD5+{bL#U>@s;&-+aKXe-1`vdOhL6U6fpO3aT}(kLq67S3*{`g z!ma8%qZhsiyo`Kk{oyAmA+dq53#VzRH$y-f%a$o2*<^Vueb0P5m~CqQp3JFeu4_kg zpFy`?tDozCXG{3??D>{C2ZHK>{-9X zAM8Ww6?=(ex$T>Q4MRvCd$H@UOuypNxsLml$D4 z*eEnq`R|;rtVV7v7*#44yyfcDuE`gAdp2_!wxZ-g3n6 z4LOH^Kd4m`c&mnUcsVNgCYkMY3+R!-RmUjZ4C&{7=S~haiqHkA%wc4-eae=u2fN-g zPlbj8dzZ2?mjjy)A=s7P`nlSDq4P!poeq+0Kc#|o4X9rLnkXWtNpw{>1c$X>9Q~vH z^A|twTCZqpmFL>lZFF~SWT(=zsFV46f0?_xOJpW?9HndH+_Vb2^Ok>w0XcTMlI}bVH7=9CS7*@!*A++#iM2OZ6|kHMED$I}YOJg&Pp3;TV2Z_c<3C1khSi6t{v91K>>y)VY_~Vy4P@( z$v#1x@O}4W;VbNKL|E^+ixWjf!yAV0-&??ae&|}Cex5QNzLY3JeusoT(B^! zuiD*&v9v^$xXngk*PltVh8R3|$u#Sl_p0ovEy>LegxGOve3*J^A#jJoX{|oz>wYXT zE9qKm-FfuY47nmfub9!_O<5Dvj!0DHhTC0YdlUjyR^(l?b7hhvF=rslrTme}K1sg0 z`)Ok+9NIkxc2GEjv5MT!HNBE*Eb9^^{B~)*Sk=aT4Q?2eY7yxXgvZ+L5pt7;VW&|q zQz2m(hasPOCuVbyxi$9Nf)~A`7v%yQo9ZCp-&aOG zZ?o_SC2Ff{?hu0G_tgASVp!*t6F{kUzv^0Q%$3%KgiN<4keYEFX2U;*FONKPZcui$ zc>l%qoZWb2YF;6dR;0PcD>iA{1Z@f~r+ zBL2!VKp1vL2(a$Hh}XGm$~)Fb7hR1U$_+f(IHhXC*wYu`fki5rT;S&}K>f^;#+F3ZOJ@mDA zGaN0<>@Z!Z3i_1O6TO%`g}m_ulh>SQ^c$J~SoXlcD%f*@r=gp{qP{E65*TDXTW8I$S6k77LFBkaC&A;V*Gfy4tTdV0?@lE>z z4X?$E$$Tj=d!mr)=h~I)Ui<2c+dMWvy}02+xHPs@`*og&9BEiy0#_Zb@jdyhm;U7t zOS5a{!A++;gGX~R-a+DEH0gz3T8d-AyUVoP8Dj-y&e-p?4v46vu}|YwL%m}ajlSof`nD(E#%hqU*OM|8%qJGv~~>Ywr*592K~J^bPT#LUpSBc zcu-jtOf_jyC&ad>rX?1_wU?AnMa^2re!Z>%I>%`+1Dr$Ju{{d4@wvuV&|^Uo-12e>z1QHGk8Mu1m$it1`k#kmP0a zFaH+gp(CG=R{&H(#GI@+g0zBJbtJ}!Z2~{?I|4{Y4;?`&D}o8CRRM90j-CD5ZWo?f zt|cJvqngGA|8yK`(Zwr0q}F&5(d*`t=&$^4@7ywP$R^o-rJ#{0Q&7rrvT4DwjexUNP_xfj3GJ`abdfbIdHS47sY7&k)^uc=&E* z?#i3@=q4fqjh`8V?dw$m?6ZRyfG;xzaK#i%4(+|oqi!2C@yp}w5k}~uDxc*)3|%mk z!JbU!8pCv`qfRq8r}UnYd#t$Y^~o0_0W6d_l!0|Ni_OY_kY<|>&_5PMBh6F%_ zg}^FYZPIh|R0`oD*N^7fhSxrwS^!neU;K3Tp6v28KVXLvSVc#uw$PP)xF3)R*GJ!+ zyTebu-4So|j#7^#eE(qyQ(o}+o#Q!QIC0xy$L&Lb*=y*%2n z>CbDb0y~t@>zzehr+ifp3CEyQ9)O1u7x5I+sO$A$at~DTEZt^sEppT5*Zp$B4;cPt z71^+C2Vo$=m29Li1DtF#B4!`Zg!pz^v1lNvB6lR)Dap5Ts25UwI3F$}va;x1ceTX* zO8C^&q_DMPLbJsEOu64CAdr&5?8!16xs-xsVsCoz3xXiQV$@=(Dbe3T1x&d@#`B|Q z3Lq4(_9DiW;d=H%%?aq(((c1uLb_@qe>#-F%(2Td!DrPW-uL?Ve)p*pc6hBup}>Ot^TnBZ#0}l`O~z@g zS?H+e2>y`H*L5nmoD# z_7~MEU`nKA9JZ%(ejc`^!CM7Qd|nN3vNg#%89B9k&ZVVBTZ7L_mExQGQob<@52xAU zc!jH$a_3VCqf@y%{HAs`9RY3<%K8`n{LU5?)s@XZe=+(aH?h8MSN;e~mpb%j^c@Jc zqR#Xv?z;ahzOWk~yBno8cO!A?5$lORjI-HRuYVDM9J0v}Ka*zFC*5&} zXbkc)a5L<5xg}cXqCGqj2ji|+cJqgP18NYKRdzZTz9rmNttcxnf&<)#9b{(#f1QKKoa7N~)x-+%fJG3K=zQb98bt{@mk~p4 z!dy{en;lPnj`|cv(45<{HnZLtuAcgQS@z6@S+9k&aOa1CxK~F^A}#)N4;p>0|Bbr% ztwzLs*K(v-aUAvdKp`W%mx_wm#)A;hM+DX0JN@?uwp0D;8XD>no_`qRGwWMlYt943 znPfjjzKnP894x}~(Z=slRO2S$ZyS>`iKRrz##$q9sc0q*rfT{MrG>%f2)vL2t%VE= ztK?#3^lurE6j^`uGSSkZo=3$6vmgG*evr?&89j9M{Hu1a;F*n&__AYA#|BLr7w%E% z!9nxuK=JOdOh~Sm`e-MyMEkSLPqv&o`*zK_YXy2QqH=8SFXu_D48*Eq#N1&4-?t{Y zhx>;zM{*y1r!!Gc9=UmgFCcWGwSL#-EQEU-et+tC!WR8d|AJ3RJ3bCZ3}CQMQtNgV zt3tNO`YjCsEo!_5bNYG6_2rJ={j9rRg~(?EPR_)z3bh63(|PL{>}V=_SBPqXan6IM zB%vF{n61|7kRrQ=7HCVl@wYysTQkmVtdVhEA7g%bzimsqf#&+Qc=)QeHh(UsF-i@) z_ZBmhz~9X1ovv{HcnpFoeyHVY8SedSTM^$g>j}p5)q@exe`PG(x2q7YuZ$k8hh9hTz)O^C|`c@LM~hv{M#fPL$UH8`pupx zA*5j1`@t@&{q_1kmgO^=(_Wvn-q&Ti*|hd8HpMezY$%Z;<`DiN-)wk@0iRxn2CmZ4 z%gx~oOC1(e(@Tg;|2xK|XhzE{bEN9xNQ;p(8QMO&w$3N^C?an4`}XfwMOxgWcyH89 z%rx+goWK+}rt=5nOj(a39LOG_PpvEMgf-Z2`>|d^5?rD4*F7#-8^{Yep8DzNWR^3V z-=9Wt*HXw`n{#O?w7dD3zO$t>m=V5(Ct`NfDd<%nilRO3(`>-3!}rB&8?WD~?0Mn1 zo7m#iyI1ezbzb-vb3$Bs&Sfagpul@%+}FUzIm1#4MP2xHV3D({Zm&eiZdmo0YpAbn zs5Oi_8qD6pxH6nkGe5u-Yg8l*M+o0xS>IXHZPsCmy9b4^yQq6`6tu)#E;ed09ulr+B;GId@& zuxtTSytwEY&Kx~FhY=LCcNX*fe>n9COr4HiDIb6=LZ$T^{UEAj@;c+@KooH z4f+0r$Ztvl#Fhjn%!^rxXC}edlYJdS3&B&57ovz3@F%sSQ@EG(x7cNM28+QlNIIFO zdJKBHfGKmRLfQ@h~`8dCcmtNog-Rzaa|1TkqEcUTUjxs0lQSu z9#hTM*-`BDhqM5)QAB2#!ysMX8L_#A{-S?LD*eU=tj}^4SRYPZIQoHigD?>hnfiaW zQLG4Ovq+<7`Lqn`YTOamC35(Uf8W|~pMG)u<5RDq5u@D+U>=Ln1CKri@~Vex8)Pmr zBq-U*C%3F9ljBp+zYFi248)^fJcv>Mx_k1L9IFuCS+pcbX@;IZa)2dRRuD&_yOn#J z4gJf);S3;}&!oX9cIZg(&;Bqu?=={}NL>!pI-BqLeRg*s+iZ52We*J?m{NRJS?LHjvcU0>U% zpFOeNc@RF2ddanak)d`B`gwYf5#I&V1Sa(PW6(cB<;J0~J7hbquU`A!>m{czUhB}q z``wm}6IFwwqkHRqkDIohIe*4C590E7+W1WENzMOZ?gP1iMGPExr~6A|&^Ny-NExK9BlkDta2Yd8i`ymCJ(Xd3}$+9<961nmLTA`z>qtdyD)!t905 zFeWJ^jh!|mi{-X{hz$5IE}FiHFTrG^e+Az6Vi5MK{zWyY9=QWwiVO>6`OgcRSNX*) z5n^p`i??WVN<`ni@#3i$i_Z7(@C5U^)24~qAgw>l+r@{k>3-G03|5U<4^d+G&k6X{ zNnX9nap}-$Xx6hN@r%JX#NWM%PM{B4ZofZ#6DU+4!C)bf`_^6WMlpL#&YH`4~KZ1sU+9R|H#hFeA^kj)F~AA*S}saZeNAD}pNbc;Ux z-!&ozkiE=A=|HM!z%QpdXu5fm%Jo$T4^qsMR?7O8;VPnUPrbjj%am();M-^k_@or# z&O#G}Vubye=x5j?H>&QpQsc{5pJ&X^-8cq$Jz1z;h$J`WVgG&G*K*lSD@coV5cfc)6kbNj%>)N*5DC9MS`om%Q^F_M?N&04u z??WH$?q(R6AM&4hr}HrbuUa1fnDhyYnSPb_+2`9n=4DNJu*U<3SLknBOOc#4WdE3b>w^V7?FDcvL@cy~f}@L;A8NZ6<>%AX$@B5zyjxLf&18O% zXB_TX+1sa=#2BGUoEFF&Qy4oP7=mCl{)I=FS;yIF?hS4AH0J}E z+K-dQh8=^*;wFFl84+NH&D(`z(4~R@@!!!e1f0jeSn?ifAqUdgkBX4zTy$>I0!nnr z5Z09sCZ*H*XMg&KXb4N4(RdXF!qgyxMt=C4i~F&y}Erh((BB}!g%;lD#HboU) z2xHt$Vcb9o=N_gMb0h$vSM2mNbTj|QCogpR{^wC!QB>~SRp(=n9uVH;Z@xJO#Vj6! z!lED!kOMi?zRL=R9;&azuDsSQONRWYDawv2R8gMuU^_bWK z(MogMWXzAj&U~oBAcFZx6iACn7={vu$Ov%p;ORMJeq27ucT3PiiA=mfJLOp?^;6}| zCD)+qdVG&T|EB^oEC#8A@Gt|GzPpQW)gs7I%*NG}hL1QnZ*h9Igu2%(#I-D;HQo9h zN8CMiCRs0LsYRK6{dOG?Wx5S>ZUrYxWil)KQ*RQWy3_x0>c*;Kpot3G&sX6ys4N55 zUY>?#OS(7m6{s&S$~Dk4O~WY7J2iNML1y1*={`W zd%2-4MZyXpu(S3F5On>`EUR7;^r}K8Rp1j!#kaPgl&z|)by>utH^u5po9vy zrG?r`Ttp)mqtBNiG`yiw2opeb`+KNH?Op5MrO4Nt8q0jPHq%vE;lXK4B78#vEu$7~ zHo_~6-~mCmeGZ|hvaUDTM1vf=^_r$|wVxkZK1LQ_*y7h`%enuAv1BbfhVn4xsKAE{ zTU7ceLC2=8Y@on-FHfNpO^Asg6CxQCozc2;k&W`Q9tsKF?o~5lk0@3*NFz9WWxZqw@qw`demQcb9tX(X5aRWU)t%<|ze^+Nsh`BGV!!ls zx}*!#56i2vS-$yQkoH((OYutN4$a(Rz6* zbNkirM4IpDxKVXolHP#IqJmg!8@}&c%fdac3>N{tOI;H~Q%u!*oZ7B|Rj-W_3@7rZ z_S}kxedh*KAO%v8g7&2*GTgM;e&a$Ivtz zl;YMjDx2qbS{Wks(^84zsH!G9;wI-+UmaM`3Z)E$XSO>gp3TQOydR4nUbqrEl`!U<7^izkE@~JAn z*uWnS&zVS5nl=q_s*X{um{%`XK2b4gUAsPwj7y`G(nJ{QPJD>%q*Ont3L zKZEu!a@qY^)}9g=owKQO{(M!;{iLdWVks{DYoGEIJY8+HUVX-dix%4aK?K6?Du{Bx zp&wL6_ZZhCY7O~21!kX=CL-(uG~TyMnRC8Ap@E%gP$$D$6DX)~xe~=oLxBc0hn?`82#0lC&P1W7p35`d&50*=m^(uSdq8?>@?LK8(EfT_d zxJpiGd)(qA@1>#OH29^m4A&wkyS(q#J!I*Ta$P?-I6hmZ+NZc;Bs+U&a-4u1R}0e3 z0?h2<;)^?J&ob^n;S~Jx%f0&+=oR#cXSdOPfpg@{@@T^Se)g91s2F915^riD>HR3NTp3a5*0&`mjhMN9l{|IuM*)UV=GS0E> z+QLZ?3Q(=K3{e1qfs0Wj6TxGUxa9|A4}=28w&J6w&up6rA~HKtnBV=Qp8jU@sU+^e z8%OY^bpD8618c)Ce7Y}r;(1L|%gIatosqfG+^B)q11QQ@Kv5P42Ty|8!?T+hMEijf z_=>X@teY4_6m`l4;Iq#Xx6OJvjW^%cIQC;|TRxiu{f`oazQuqqI{E$15xX`zA1V%NL;Ws1PWEt%@ zO#peSJnDw(|NJ9=BR2w|!7gVrRQHv)h4EjoG?~5N`X{-lCL^S>KGsDzyLh*F*3Qsa z<4TneyvMS}qpZR5CF|jioBF*E@EFC1 z7g`R5P)!hR-Yh#8crFdwT2)Bq;#P@$RShqE-|+m4n655sxM;(PsJeJeQDHQ#=I}Ox zYewJ;Zc2li`zcVw`J$xcXr+~$g)Pdj*Ks1?>5F)nav;Eud|)uTjzJt^FSBUbXDWTp zNdY42!to8|NP1b3z2*1ABNGB|QCgqy*FawfdqmjdFB7TmT@4I7uKr^XO-pJQF-TXY zJ|>}B*K~+Ce7N{+>=tY5GTgR^LtO#>BIRmbS$9mdp5N|d0w+zEt9PV1?81(|2ZE{W zx8v^bn6NDWbiH0sHGR`GG?^V$F5x&3>f@zF7zYw#dk9Sy{~kY+bq&GQGR?U?4fsMM zS9M)oeZ$C1^z_=0VW|Yt+;!czpl}Z3B1x@qklISG&Cl&|LXuj}8xC||B~eR9dsPnY z#vn;ED5t3lgyO&_q(f1&_S>q7*&d=J^})2+Slz4i+x+`>fPZQO6YkCik|R)h5P61X zvtR6k2@@zrku{z_FVoFtzMch&#=MM`;8>0+OP9b7DE=*Tu04v@;WZGi2l^oWS};FspcS4TOe*?!AI0HFx~T{bNd7yQHI^fn1b6qP3eFT@MAwszmbIZTh+; zT}oa6eGuH@YyGFYId_hrfdVvn5^8Lg6pfylKC?J#!xic+>r+)xUKx2gPt>}uytGI8 zTlsZ~I0IE05I%rzh=&Brqk6haf$ilk&rI5t+0|d@9S`}rj3lu`UB!W3)U7WTiB+0` zcYn*6cj&XDd|9>p8}U9#(-X4$z{C%ZKL$NFR@}8eDhI?~f(U0Lv=ODhO;&K&=KN*u z@s|8nlKTi~<_y=VEELjthK&(X$P6TW>~vKsRNoHLCMNoaZj{?`F~O}8%7VRQDSBQa z{N6p28~v)^B)iuGr#Tnmx~V8DqZE0Sku{%V(1~TF1z@18gT#Er)h()5-^+PMTX3sI zyiMorO(JP6>>KC!2gydy{@*ogFjqi|4A2XyI$*%T6BvDu5$>D!z54}UI&K7BFRD#E zJlzFS@rZt)^vT?_?^?-;7p5>>8l*{6y=9W^ree#1PV$eXpQwV0p*6EnmSfQ5-4pt^ z|5z0BBsKj0+_B&zGw=2L=SrNVu}@_jOlxPSlnb`?xhZzgKtX|j9GrxS1ddqk{zx6E?3R(0#@!G{yfr6=o|c7c|q_+A>2 z4m;!k;5U27atK3l=L6_KiUs;a;*;ko$fEB1hq;G3p0PCEeS&Qx@7@D)u9~S$=E_4@ zr1g=!CE0^6$_&KP^Yy8%qmXjkCu|n3v^-qYvR&!!obm7IQ;Ai^=oxca<=~h$($df1 zLq}Mr;M#j??aFTaQ9k1mVsqIkt^Nsi3f5{uFMpryA<0~Jms`Iqx&h91y{=~8BXr;P z`i-UMGO`6OsthUIq|?zWKN#hK;@{{M1{?F?3mS|>?(&3ZK7g4LN@l<}(=nW9i?}W- z$HiW~_--=?(TjSFXJ&BGoM!}EjKNI|u7TBUE{;V3@@RYeh;IiQ=R%z_51>?ba*{KU zNTKrsUiJe2(&Q6R8P8o~qAHuyKF@M-($wa+T4KhRl5Bb>d|&r{nEhQHmU^W@lU2lM6EF+JYNTK%5EY4w5x%ufcPWtYwe%-B4(*Ia5nNQnm({vBb_&e#E(q8H#nh=#%;bc&f8~Z_DSb8q`>pRf0 zvEOtRfu8Qs)_*9fiaAn9X)FZB9^NRhgy#Y3;6#r3?6YrrLYt8>Wj)&A$Sr28tzQ;QZ{A|ofR*;l7^81msQ!e%$4rpG3JD!9 zz`HAI*nBX*KK`2l)|qk4H1H7%3i%7ybXXLlQnR_ z;Z@`m_-vpf+()vwxTMCzu>eG~ex-2XwpWoRc=CR$^SkccpQ=D02O z*x~*>75&kB?fD|sGT}vaS=Vey&y5Zw<{f?6Lb~Zof}_SM-;&P5f#D+plZ{3qRA|`7 zJi2hinvuKv8TJuR@qw)c_>iZgr+8?Jxf5lJQ`}|70Txlv&)9V)IT^s)p!9Ck(o(}| zIjz-V0ob9sxxdU-h>Kg88-om}Jbk1bqpKcoC@mWiKU3q*exZMY&pq5=LXqL}0C zjH9x?)r-V1z(sLQs@@ zn9~dZxq!4asOn_FknL&izz0;Hthd><%f2g~;86K&J4y$iJB#&iM;il{iR|#Rr=S0) zB2&88oX5~ajzZ4T1wFyZa59!}DdkD?nuNcyo&JT7vp?@W4|r;Elpb7& zZ2z!1_F%N;lzWKj=Sx6kEb0r|zLMSxyt)l)CT$N-G2S3HA2wg_^~pn+SbcPD6dt|; zu21hBOX`k!$JfhSDDEKg&ayd_4N~Ar&j+0SXCoJp7P_lP3z&V+QrgWVnh_+tzqHujB+SS}&3wdce#QjDb~)>g zL4$`W4BNs30M~rrL3Lk2a-jZdZXAP**5QWJth-`BAna_!&q)6x9+Eq;29Xx8(<|U;W z>D2&5-|VTh$E|fg96UseYm?6^UpSZHY#z#dYQK6mkC7)`pHM+U$)p8=azvu37X0;RK>^hyjHGx z^6<5C%F#{0j1TcH&?Dtp=2nl7ewIpIS*{0Ww2XGaH*@0?O%fJcuSA&)E)4K4%M%9i z*YK|RsNzEd#t`_N&c&dFBFlE<#fDMws^{!beeI3Q_wLUmSUi!c5!!$O)NxbU^yZgh zDMnkb2|!R6`+c{jzMnuim27{tXBaf_`Vwz_Nfxggus{%_ylNF96=3g z2TCpPi52_`-o~!P;id62^MMvGqOpKBF z6wAj$<2*Anhc}^&f4(|X*mL5u?T*+X3&q&q}IEz?w;64$ks z7B_O8`rE^$Gw?wW_Wx=mJAgA-1rM)GA_*v*ez3Q1r9T})I?N*MjH%75R8;w(C>86n;Tt!-)yDz?Kj?u$~uP-zDF5V zX&rQ=4X*#x>^mJqvPksD*uUY70$CD_CJ&+W6f#6`q&a*tdQ z31_v)j!&^x<#$^@^^}#K2$uTS2&PJEmkTdF7tJ3j^C*X^&lQ@C9&rsSO)5nCUu(**5;E6ttOXh6EU*4eO!Flbye%bUw1D4;(v;` z7#W@o8|ca@K`+dw_ySEUk1m#s?|RaBi;+NIn6}~!m`fjT0RSw~FAvPi#`EV5zH<8N zpZ6Cz0vb@4-r9}UqbILE43BX?Rip9*$NKH~iVgQ&M@>*-538CGC6#A58G z1AL+QI$v}KDIHvxGAnMN=n0n^^W6zh#J>odrp*4b@o2_|)^dfYAdvl8G(VHz2W4Sg zJFi=W+k?@1rciHFSdUVCyjS8X<7=e3S)}i6n8tqs8Ga_m{siDB4s>1^AEF^^xH8Q& zor9@mw5-4${y6XSJE_Yz;=Ph3FMd4x{fyIHk4)M9JHW?@$iFE`Yso-DAwYXy*y|%f zT$}3_srh*wMof2R7epk2xtS z?UOuu)Kxm8>Z0uaPvDuqLOifESM0b;BY4rT(JvUuwO{*abNc$^a*_VH7xna49& zm)*|xLE1LK^0CpIH(qOUW!-cC68-|1c+)@w=mJ$u)BlqfGoYs_okD(c*TT<>|H(Zg z-ajX-zo>=(`G4YQrrCgMgvRy}RKGT{efK|cw1TQ(ApUF&=(D`3=lq9xHJ2{zbBIt6 z(Gf>rc98@uXo!9<4iVaN5-8SN`twNF;!F;f*owZNJ+bRzVu_vXW+*UD(VYnY!L1BI z>U2m6M@a)o{vn51*QRgs<=5j-QPX!aEk7(S?qF91ccDia6<{J^7ePU_qR)2CzihlV z^nCcz9a73zKhLj!o{d+T+W*dask%J0u}TK_FVIEd$Dpe>hX9M+AHr{EY~oem+jR`i z%45)%^d$gqNw`cOnEJy~RFx(G9e~WO{}v+1ZksSTfW_-e@e*WL-W78o58o=o-vv-6 z0k&^A^&gsJ6hKlLqu>0P4(tF0!JBLU$K}!!L#Stm8|+<~lP~dn^=^*9X-wUT*f;!X zYYnBT9;tI}5-{oK4eZyMB6s@nmh;nV?+@=GGI!q%DXOxl9D^<(EKLU2J3<8?R@S}< ze(d6|VDWtB#JpOkcw}2}=$pPXBD08|Yrkd|+e>6pzl}_oc|{B*;Ce-iU+k>F#rjM_ zu{Q2}Ss4{|E;rW`(MWv$p8h+!a$z7Jje|cds{TpIl3Kj2=A9mQVsVdh_qb7Dp55P_ z8D3kDR)}yvwQhzG>VxKL#tDxmdFCR}rj&nv(mto6kS-UAe`Qr(85sX^x(|$+3x)8Y zj*4I1%CcHc)Wm&USLG0rcdU%2HcsZ`qo2Q5U}Amp+V32T`*-f}{RPr|rsJU)-8+O} zUTZ9CIHKUO>Z4Jq#WEAFbtBFwRjak$YS?hIaVJ)&f5XY~u3drj?^2!i1U7eSOP`E9 zt_WHdu@~oO=U;1Az1S9-+pp2{HrDN6BdB5<09n6R#LidKu$1y&4DcUh7vyuj08YS$ z827zWCQp}Z@$==w{0;_@Z%UY2tpscnw&kVBjita+%rUm)d%)G+&n_YN!EJrHY3C?RkJRTrK8g28g&fIe&*bh2KYRj~vy?6KG@6We-9*>MuMU0ibLl!X z%~|fcQ(&ms;Ac{>y5q4BVEyv#-E_r>*|ta>Ff|b)g_d)~4f|j@+3T3HmVFO$zG>GK zm3+VB;3S418h+8Z6dQJ)Dzb6jW{EPnJn$E~l2R1ge|AZ$VHE9oVY4)Se(HBrn1V!@ zp!t%7-}Mt0@XqojjdJMLgNqom>F=~!=}V<4a~qIS6+DZ?o9x!oyKzMxIzL`hm9<^_ z=iMh8%oA+cgLGRMH~y#q^vVt#&C#53ehR3qXqLpCJIcGSkYzQH&CUOq#hOo>JF#yu z3k$y1p}Fg7ltNT53~(p90h}*R!F<@;GokL<_RX|y-^Lz~UU|#xwV)KRC$KMTy{dEW zlG@LugI7=G)3UQGTrc|nJByczyx&J{y?rsHx~$T%*-lyn&ruJ7yjP8Ib6sh!{pf6z zK7aLvvfeI#y1=IV+K*02_u1j+RiD|fn#Hs)h`so@K&ykHn>~tjB|Ickp`G%;e&ARY zY}sVLn_N6J?^dr!Wi9>PM;?y6OR?R$(J3PxVN%sYdw&<-zOsm`3NtemO{-82%D5A@ zaH1pm!rJ^#dASsyLa+Y#mbFU{%e>rrZN}ZxQ&f&7g`qbSKQG~Bzo-mAx(=XrC_9sU zywiC()XQVgNmmQxHFh$4G1%Nq|G_Z5J!D>b-HY*xN1XN28OLhY$`Em@j@!11ZRk_9 zB$DD9gjDA=svr*`UEO-+4L-jLc7+y+LzfQ19tkWfJ&6%I;cI;^ghM30W11FkLBX^@ z3nQX*A=sDHCak4&U4X3hth8ivHEO;3=}8r z#&Ha}?w(t2Zl0g=f{b3p)s!b&Z5Gd~YrPjM2NI%gVh-jFRlOr!DDJntyH4Kra3 zvwYsW@B99a_dVX@_s{PS`KKAr^W67yU*~mR=XG95br{DozcBrz(9AW9Cc$M{S#F(0^y2@c zS;>si(3ekWRrB*vK5@YU#t1w@?LBF|XRNHZ0U{;Kd;f#N>|C zqt^U~bDO0HW@ONWjefEM&6Fp~Es%?HR^%zKJ)sBT)9oI_>q|pOUW6U4GR5zEWm&nE z<@dfYeTh5pAK6EsZYAqfB2fi^?E~Vt^0#V5cj_(SQ-AxVVT ziz9rLuzvKk#M&k%r_fs5Y}TirXGAl74iX6&owBhSaw)>pB!L?2P8Nr5{^PTzJT%O2 zw=P#+JT8>}>qBK8#jRCL?o{C#%zeLOlVaCuV{n=l>$NGfz9SOnNh^tDhr>jS7;+IC zdaHP68<#pc>}c7l7jPvSdD4~fd#<5-5tj|5LQ zbcN+Cv$vc6M+4V8gzD}AnE)1;du3dE63LjVFM9Uz14WCp4jP}dQ49lsK&1;fZ=eG9 zT%LQ>0a*BTfQ!!`MefX16yv=m{FrPKd8T(E8@NB$5JB-YPaHPF4gs=_4pVo7(jpf# z+ZAjj0_xiE>2v5P#HxLH@~ndEWYS(IN7eg9``ZgN|IsL=D?iCAKW?X8`lczt-oIS% z-PxtMKBL9Gk^qB>PJ;%%Dd!b#GDpoyWM9^j^-cG_d9fyu71QxU>4sEwWqV|(pWhXA ztU7%?J(UnD#0hHP$&%DRENiZX~F$d=f2QJN!fD zkHbZDBbhOanL;cR2QJtJQ+(YVwYUOAiuFA8rixU>;8y|`-ms z%V=1;<`}!XqqC+xi`!%j`FhXhbKzE}%cOI0+$~w@{T%%zgCl`k;+|cO*ie9oyHK0?I;$VXAUU!35 z*}?l;8s zm?=7}DRHvdN+4S$65b~cDF&~u=q*EUf}6j{<7E1IC`b|x;Ikw$MUBkwL(-c1XRsHZ zwK46V|Dv9NWK)hqGt&9|rJpNke2HuwosqdcZ#N}@DIBCKkkyy@_s$c;`^`pocV$#$ z<(Ac34BiP8){p6Sj_N%9>iFdR%{S;xX)3)axT-3g9(Hq)NxC~sdfa!9k^H3Q)zkK| zN=whBNihNbI1wZM7yQ|m_VXoaJ~P_7U}6OAEZfOXj2x{T(e z)FKx@Kp2oulA?Wx5zCMmSr4bbQ#-yP0idJ9vj0x_!lfKZnikIlZ?zkV?kMxB;{?W= z_r^J;6ui@>WG%6e%@-l$i~WQjL~!3{Wx?GZ1xoi%?%P?pHha4npUXP4>**_dNv8NA zv(i31GYYmW$jpVB~GKozyDDW%`!Zwj#Q)gg*gD~9C z9p4G$L9^RQ0F|j7XiYYRDTMw1{);=6sk`wmX0bx(w7K6S2~?TX=@&LY zmOT98rqLmBHkS zl#w$Lu)h0M%Iu0as+&{G%db98?8DSbQH?>J2vc)&awnY>N;bs#PGI2c{EE-GGK3_s^9Q~{w|$`g^Q2xjsBI61gUby>yO?@iJ6EHpS{nF`3t zxX|_Jr)YPtS%WhtZ9U0WBHaw%NZHp`keq$Zyc!aOIy%op%=}O~R1&E7(4do6b-d+ucv?q4 z9fOR#*t1v30x7WZ{+%k)Bce26kSJXkxZe@ndZRw3Oqb#EQ$ig57ls)dVX=NicG@ch z;dH6K(!qzn3B-{b-xW8z0PFQ} z1NnjkHHZ}61WiB+Xh@Q-la+swp8{t9W86Zta^SZHwT4s=odZ)r+uyj)A8OcX&Lm5- zxJrb1zF9^ra8cfu=`R@Q861a{*la4d$xYrK^W52~sMy)TI#}d~pYQSN0$ez#7Sht3 z>L}wR4w0CUo#s?Pp>*m(7n_1v$f763ZJ^P`Fd);2a{@9~qdP+TJbw5lF`Fpb*Y%Je# zLMEAS3gqUp7(j-|KLGKFkdAM_u9fJOQ)iL9d!kY5fxE4K72i_yjzpP08)+v!eQ!Pm z)JV?cLJ!Ha1UybK z=`CjknHGLM4T}8s3qHxy50}3gG&dtawVB)mdV2T`z{s5Cz=ao-DI=M8OgpY$M%LCT zMQps#I$?ulsmvGyc@>O)^DVecmMx?M>P#(?sMpRIsAs&dvG--8-k<6;*Fv}z_x{<+ zwaqlx&D=kn?11+)kNX+VY&Dpf;<3!mBJ~5Ca?8>#LBeU>1+5xvjB5PpfiU5u^6>F!bi+ zLco#Wc$hC(gdbL`R>tIu{5yZ8YPP~;$0|OGzq}M~W6)nRbCHp`C$A5qQ-+VAGJm5K z$*hl2c@URsn)l^J^Wa==$z@(Nzb7VMC+(?{+8aA)E4PNTTT}jOh(@P-%!eoG1#Hwo zx&F6>r(^H`U8`3`8A5U+@BghwiGs}+9*OL{MZO3k^cs=gYX!!woSA9P&Tq)A+wh{n z>ifiKm$Kv~)>BQZ!msVV^>Siz(K7{925x*eVSgO%N!D5pNn#;tF8#i}l)$cAH#VVx zt9i25cvI0)Mk%%QwYs$XgRr}wnq%a}(;y3e z8LY-f_Qv;)<*D?WxPC3-7|9nQiYtyGXX2=s=J^vcxz23xA$56BmJGOOf@GxqyZ2$WMJ7}>pd1}2<3 zW;)49#_F)>HPky!x_HG|p#fy>1!J z?#6Xu^lEA5I{c7fAd!8Z=U0tdQ^z8+zA$5&<&EkP$i?`T_cLtfN9?PH8>xoJZeyO5 z`RKn|q$ddG2!b=ERPz$~W^H?>NevGEMqW8Cr0K0pCfjW#5%pIc_8*)DoUftL!0`Zn ze9sI-Rr~ege`H+yRbXhQU`W!Y{-9pRul0D}mXEL7o_p5QFTv)eJ_u03%P58eP6Cq5 z55n-3d!xu5n_r;a&uZc?Q3Yx?O!+wAR*K=S2{^3B#84nVbvXNwHzGM6-a7mL{0Jmr z0pJmX^eqr*K(rkV0;bPIQ3x44cp%X+?5Cz$PX2_|(vg#q{K(&@0Xo-TMduO5dG#~v)I2MggPk{G-!u$ zQK$Hd{=_Kr@-K}f%JU4}q@p~;s(wAYE?y~H{h{SznuubRSjN2j{-YIusCr3yWX1M0 z61Oe@TuCyfiBkujR+n?&_CM!%tTl&P@>YFd0Of(u&rWDO+s-hf8drVo}k zjJZv4`kT@?LuMw= z$b5qFKBVYHWoWNJ-Uuz7RrXIG>7upoen&p8zsY+s)kge(2=As!9|e;tzaRgdq7WvV zj*Ec;k{9ZVH8u&cpt5dn%Xrq-PO2vN8F}?2UDm$YDav^uzq1V(^Pgl}{BD7#7yhCx zZttaUc~stc3BFH7iCbKXmxOX!$0Ep*MZwe@aFLI#h62liaH`7;$mKx96A7<_N6$V` z?b}~daxsfbn3oWK75-4DP4i@!pqttm;Tzb}90`X_FNPbX|IyT4jyE4$&-%lworW}h zupj%#eYHsb>v3z1;XrjpW*VH_28MG zxTW#uYh`uBdU;M5o0+MXqIb?*n-G!Xl zI`>Ouo4!?jMJ(x=rs$}4zkdg#)Nr9k?m0SUwU<{bvs8|q@sm`hMj{@!{pcN0b)_51 zraVhstJpKY-}00?YtC2&vO|BTlgZuwII?pg;wElxz6R}<9Trz;aVcZ#gt{h+#r{HG zY)LNF{M3Gl2zD_YJ9Pv@SH~a6#JtjjVnsg{SKG+Am!Z|~pn(-yqW*GQq}CnHN~63u zRdy7AH-E9^^Nu~rml**#WQV_PF zZ;CvI9`+l?ia{-OMt{)1%&QO}&y4WGxUj?o$ICAF;ugBwuI)=NY|)2$b3;2htNfhy zqEa+J;9-XP;OdM5pTRs~5!3j(g;$p*KeBs{*tQ!fCC*@k4tF55(8U$_O9;naehr?R zEDmGYE$mQos~1@w@xrdp)W)9+9Qyd`SIP{w>iCFPJAHy(VIMSqihq|MK=Hr&O1$Ui zfj6=^e;oFBndcT`Vy4g z>N~?g1yDPAc1v7*_@yapt)cPuk0{;FPIX&b7O7j*R`)+>;$=`32E|q~2WqJ&SiXlz z;_jjCb!!r&hwF?^$tL3*qCq$+{{8cqZqXMnc%vkhzfse)ga9z{ z7bjzd>|J2eHPDkw)v5d(^B;|&%&JGmW{>=p6Z1cy^9}i$Dn-1$%r&ZEyM$s5G&hS+ zCRp|5$|iKJM7&!weVSC{fHoNpYmcX7Ll-MxwazHipfVfyQ7YWqh<`&HgqBsZT>OXyO9{p7BeoRhLi0sDi0Gy z9c2$k@xEj?l(hbAE>=$-q`Q=JTIpVB?;ui}!_=1Fss=BS;QASuW*tq3lCSu9`kV>B zTPfnlm>7S}(NQ@3n(*W>z=3AanT$y)J)XouZ0R485$!S2p`{u3@@Paw_h=dEsanm(&GO?1IVe3(i*(N!IWE{?-@LLj4MI z1XRLYOcTsan}1OdwrxBQIIV;>~@B-#lcq3|sYT;7u;49vvm?chbZXKVkr@L{yn11pI(H zC;d({Ax2iX-7rMOv`f34^R?V9NOV!2F~QB`n_cHGRAYBe7!nJ6Ij&gO{=GWCy%Fz#4S7}q(T|{ZeM&OTN;lBEo;lHH`dsHSBO3U

bAeq$d1AOD_Yo{sEMhEzi+=K)pBaRMH$Ai5^(9NF;)(ghjO&WoRn zl8gKkO}6~hS!yvvwzK)nBh9kpH(+L0I;gd3Zf^azK)q%WNsNjh-2RUypTP#C2`G@| ztkfODz`4o;;QMt=fQ>sf5U#k=ktMt6kdM~HPeUd-53LjKg)FG#r_SnXrRO|9lQhOV zQis&%B$|WIMI#6962Zrw%sGJ8h^b$LQoTA`VG00GrRZ&-$^3w2JwqT`gRMYM9KrMJ z5{o|0y9WcaP3w>uj)q^()e86a-`d~5U({~bFM_cE4JCXmQ!LcivOfROTnGhMnmE}B z!MV_p-$(aBNL(PJJor4h7wXDWc2Rnk2%%p3KmiHQhV~>BSNsb~#0CfK0Qh~zj)Fqk zv&26j?k|l*`rVZ5sVCG}iuat%%}t*#qPaFD7T@~vdY?|HsSZp^Vh5bKPyQDc`#=5d zNE|V!pF}{)Eaq+X9h`%ll`qcr%cm0X)tt^?&ufXPc{pLz5DvK0!;}4dYT?#Bne)Qc zEt7@gPw}>OBx6DuLymYhewtTE|64f?{DBr z>hqR)OlcVT;igvPy`<`mi6UdRhk6;ZS5&9{Jwr-7c1P<+4Q+3Zod`s;E?s{V7vOFn zS3lRDK|(FJCsWcjrCm^*?L0ww+*KjQj2lY1{P$8S&0uxbx876@G^no(uB^VL6YN)6 zf%4V4CoxhdAAZNPDPv8BSM&hW8j0j=gh}yt982NvcWVBA{6Wvj;&&uX86@))B^&9E zI%`9W=msZ+?+C+|9d~=9`;0ro;{+j|kF~Bpl!sN*GqgOfNY^DkUQ0SdQtO4hZf5CY zxnSpdV?Ea~h;zK9qk0G~P;I>n2~*ti|8ng~=wtKz3&`mvTWFbS{2#+lI~8M)K)XliS6a-Po+^1zcJv)PKIjHeCj$M>N^7)Jw4H1ur#sD|9U8va*v?l9`!Sd3_ziFZ9UMkj?RL`Q+MGK*y`i0z*s4cBsi#|LwGH zf_Fb}j$w+^s*(BxM)EMbTeKC(ZH0DOJP7y!4Comi2`xNUuiCvTZdJ3DpG;gfRtv=QM;M?eR zC)p?=OJrz#cM-r0)o}(9`2v#55@mFIUgFP0HU zx+<1!9|F?$-k;m!!XbBSbDy(Ld#I)~Wv@TJOK3gEAAcWDM;VQJU|M)NuL;jWh$RmA zeI{!~_3PmpUe_)#Y+k*SIhp;MZs3=r1tg}(>*D1Fk;+xtOShjUP9OJM;#&~z_}AHj ziJ&WhUbQK}OUCLbmnkoSiIBf~Eb>r8p)jS-;BF~nZzcV-gLWAF{W7E3>9_gDr;HvV zS=zYwEE}h6n=~7aY)8#8QgH{0C#I|K%LY{16I^{!hTI13pIU5dD)7{jGQ>UHYM5|H zNL*2UE9y>bJWEDjU`&dviDF?{QR`p=y_uQI`ZT|9ij5Z`wLL}J&hq>E^w($a+5M%@ z>Ff(A9lx?nNeg-^qL}P_v1`cB7XAj~4i!Qu{;Z3GyqV_=mtJ5yuNr8zZJ5E}X$pBR zS=SgRaeYO|s<}z>(s^l-YLpYsxr^nsRc3g3T_fW0v}{!V3)w@4kOU)oyUB-lG8T)c za}gVImnhrM6YmW3zES_(a8`!H=fd9BVJNY1wRl%weM()E&|1H#|wLuw&_#Z#>s*ddg((h(Z zd#?K((-|+ZL}r^OYN+G-Nc!zbcDTLQ8kZ_ItSWYyFe#$eovev>QR|8KdA-XO6-L~0 zIwBUidr+tPml+bmR%}vv(~R|>qlFeM5`yklkXxW~P6<6STanh@(H@~YHaPj-3cVgG z&~?MQVbM2aORAeK_MU`PQPL<`nt#Qu-wf{M-K|PD`4$hli0@S=6?Q&~EjG%ND0kB- z>(t%PNR1!WmqE7<+m8 zhE%NAi-t@Q#SZwKpTd2i2q|uK1)>HYp*yZAjL=Rq!%M_Tc0(@ChL)0G(GQwjeAT~x zOjLJyW_h*u8_JXI9&7R-KaKJ`s8QH&3}lg#7RI|F#EVNWwcJtzy^>EG;uU{e-72N0 zSv~#DA;tj7k#!uqNMw11e16jn#%HxPuaOF8-3&H*ZN{X^FxR8W_< z%FfJn=R#5JpQJa^jt3h{)i=hi(s?@72H!t_>jww-37?zOFH=33k&hC2RFe4GH^1VxG`hPLR3Sw%km{0=W6l$_p3`^9Hs9Kr z32yWAJPOc+aavDLq#OE$`cd0~m;x(L(hh_ZOo(jD$Twj!%_O;qC3lIIM$x@*Qw#!^ z)d$4lQ$Ftw-!S*HqXW3+G9?<+Q5FG)t&Xa~jlx+Cmx9oho_n5t9QEANj6d4fSh&{a zqmyqriVFXUhv=%d7d=_gwRoe)ie;#Do`S(TsFs>H$xnV^T%ZY5dHh6qnNZORlhdAM zQv$P@WS-O8kKn6Y>XV*Gr=rK>0901)UnU1Uug6@}d5QriZ`FzV1?vvCPU!dcXF51u8tPx=P8isjfSKG7NKZI=F@E&*vU&V$gRE z?6hDb++@(u>J17{wP>u*C-My(rEAUwdfsHcZ_AfdvsI%QTB7&)&20{w1HT*r0n~;+ z24GCrB%;crKB23`wq+(CP>$x*fO-{yDe9A(EN^6;W49K*`+FydX=`0M^9n=XlJ;u8 zL^*frK;U9*$I~h_TLMZt_Q==2k#4+y%*sB-0q4bWEPV{zHAM$K>wp*%_f83@i7J0Pdx#o;0ot11ZTWJRJHCw z)nsCo7J;ox-VI`U{+!-ou9=|yjNuPYXQ#GoE*8Gl(V&ovfhHZI&uP%SU#Gp}%$km7 zwNTF*zn#6uUY_G_AQ@6&4ygWDpjyIJOwb+2ctVJhnek-lr5Uq!yl(c>N#77t!%>qZbJ;9n2dfNACk=Cozh*)8*PyS6N5 zEY;KYG_Ibj+I$jExL;g)yQ1#%J(P{4jqLn|;%OHr>i#jnL0@Y~;|T6wzj>Afp-W-b ztDzhBUx}D)59v|@J9*I_06Rw9shv_mD?*aYe{RlTenBI~*ZYg#$)EnS;23&|bQ#&% z0I6BnS*e)W))cOr(7f!<^-V}&Su%d}=hY3JLbuN=l&ttTtm&0UvnPz*HJT6=*vEKNU;gaB;dCS@EB3be|)i*sqc@V#= z`es>I_=5%TxOf=O#UzG10DIo@6n{98`%IDp<%8ET-{*;hx|=fSB*m?_*Q>k<-YrKa zY1Qr7g%b<7uM|&84svy_mG&Rq4S+%6&q-j7)Csbq#T-yz;b*DEb3O%ocs&>MiaUUZ-xved@EnyGu?Uq8LqYs_~WeqpO!1kcsr{iP>Xtfdu` z`_=Tp^#K2CG~R|`6M?i;CIMV!PKk72*^2WUl^l(IuJonfz5{nxh^u*vpb>(_Yjb2C zRRv_=pr3q)GY)-;)C!|_`mwJV`JIA%Vum;~@U=1jG_w@bOZ=7qw zE8cwt6ub44BSJeRA5_<-ZWEs1Cwn~g^SP*OKZ`Mm*ZEzV-pdI`d|ubjzSe)!#=OuR zbjo(>AnlnzIg*keT&WMPF7pZYPpkz4%bnQxsW;sW!A$HX4TY~{Z_xD+Lg%HG`} zSAK$SPlflatQ8hERg9`l$%|7|k&-udo4`@wzi)!cs)X0k&#x~#^=&@$m-*(^XTXl+ zJCy&eMkr{c#6jIbb2h@Lp)O<%S6L|DT(3KQ#8q9;>BE=z?T5kTGdOzQugjSG6=3uJ z^mIhm58-DF3_MV|8WKB1>|aVbi}CWxeDNr9@u%%Acdxj;#0=zAndI;Ul>>hBhtNxv ziE*)jfxckXtHEuBLeV?pjV&T2_Z~Z2;axv9Io6l~LDN^V0x{(y=)n%iC)RWa;I^{q z-jv(_@+Ez4$M&#)_Vp0bqf36F_4S%aJBT9Tfb;4U--W_4kz8S_IF&*;{!ypucfq!H zZ4|4;YK!Hr20dGx#vPo&4OBaCSU)8l>O{FnxI@VUCZsdnX|Oq$dG>0q7sesNsmtD$ zxvSnZyw3g}G&I9ta;JTBVtLuggRWXiQ(7VZ9|Bi$yxar@NQ+z=(h*6?7C;d9oGq`96n{ zhupnhe~vXXmqdqjEiuq3;{zKr#`c55IGOnGT%wT=U4b*!p<*?vIKo7KI&uYne0jBH z!BsyzG5pyJb$g6vI|%um1zrYat$7!TGh(Vtxfv&zsGb_V%`Au9+sK8we$4YoD_OU& zrihjDGETiTo+kb|yC<0tiJ0|cO+*UrZK12gd}1Aq5X zIlB&$W3k7@D`@OPJe)9#Z;Q{nh7X8Egp?azjyA5z&`oF8yQFPBqkCFZp`*uzUbF5b zeUXKz{2Y!w{|s;5x1sa-f>Xn2t16-&+EXTz$z$X1z&r8*{R8^joqB!~r$(U7mNn?o z|EuGX3ChUgF9lmv zTr|P~R-OhtfGcP~R_(p!i%uZ>IRSaWMMUWH7}=at>n?nO>J#Hh(~g$Jm3<=cYcIEx!|ht}O!bRKNWaJ->7B z!a2oNtn4S7aJ?}}NLB?4sJ?U-YRctUymp82YFzg$-g|VV>ZenleM{70{^~8}*~QeL zUcVd3Zv8v&DldQCUizc$mnj1hAdm4DNaHV4TTw$pejsg#$t|dhd8!Rq@t5a}(VT9L zBON2_-Be}+V!kV$pL!u{*+5-2Q3scpRP2)4?0im(t?`9%X-;afpz2B$I=t-YHUY5p z5iSTV(nHV{t4K^(Ld9Ac<+!dGY8_rLSMgLe&loiCJFsN?>N}U(WGZ#FbwzTJd>x>; zOxID_K;-dwrha5D*t@nHVgy@c(^*MrHvIMVz!|N0B zizYzdXSpcpe#ld9?6-@z6~2H0MS6?;C-AMW!K+yaF zeG0tyyD^Liwa^kbtLNboDQNlFqolNAta1U$^D5E`Y0|l%6P0RAZks(aQ3gRO&qz ztD?RHx>6)j641fOnV~MWO(=Wo758#|xA^)SZ|{2^3M%K}hTl2zc3$?5zcpI{%fj$r z7DuHm>#i3Qk@(c*iSJ3f<*m0r#uRB$$9vd?;azN0L7`Davy_u`X2n4t8Ts*ji@y!+ z31vryZWx9Dr-t4UqPle#j;QL0gLH)kuk*5)O@RZ#XTh>V>kd!?aXEVP zTtx*OOld1dan8@m8JLv1|Jn=*@19C^=|UlMHmBQP;(`8Q9A^igI@#|Im*1P zkwqp=9`b`%5y(v5-Y4?`|5(2TYUb=@7YcvgoMIzSwLnqu=wGPcaDsI-L7MVbgB#Z_ z=w{3&`PyTCcFWkE@W_8eIZY|Z8ueuE(y0Jd8iBmp(qD#_LgxHlT(G3ht_=2K<+qgw zsiWWh44R0&fN<9f>%Z~m71XUwZAH8&eo}}NKOWhR3h_@#&9F+S4${8PqO&i2H}+ye z-*r9Q{;^M?+oaXGGWSpiFO^*E+mf5DxF&t!l)H|8PY1sUv+4=QQu{R}$+Eb;J`}K6 z;l?|~6@wyuK$lJ;@oG%CE_cz4J?qZZ(fFo z$YlIZpwBn7AD+z%zls&;)GHWVJ&CD<%n4D2 zyG>>OLabSor?KADyo!^LM0k}>b+1W$kt-Z?Y7&02|53U?t+JCxGa@w%NvXmtmP2c~ z$(+SK&9H7FmO51Eb`-;z6wxXRzieydIh8alo^23iLi_Yh!oA(|h0j9&;7C*^L0n!| zsWk9{G=Ec9CcB_)WgMM9CN7#tJD}1*hgN5KxH+;Rzw;EV;5VWY>C9$4hM5d;cP=;N zjs0WW2CsK(kP0rzJ-%~|_iopv4v@zP{IKeIuN&I^?nh$#&WqUG&s;NRy?-vbsVpmS z5)BNwsgb^qCbK0`4MXg}m48|RiWiyf4fHfE^)564vvVpGBr1Q+hcP%^=&=emgxZ&r zL=me&$9;pY7Iz^hn^E~#aCWVlTazDBj4MISg><{;!oTtEKniuC44ka`KY+&plu0;t zg5Ne^S^kZ^!^e%k(B0)*{y8nlg^FF}=V_jN?f5KA_w3}~^jA;mML_+90{fK1{wZG= z)8_tG!04;S!mE9w*J+!ki)ouW_FzLOtPjb^eW9Kk%d`xCqbZVcJD_q-X=WyiK?3D* zEox5br^9^~pVS~RRO_N9ccN#kpBKZ?W2_(fI~HPu-rYjz@p~y(w<;D#Bil+sg2Y=6 zH`=LDa4bFPZr}`|n3xo_JR%erPU^WaJ|(QLzO4Ri_|AjAt0}sQFW%6s<`t%O|I|V1 zniO4l@OTrn-s!t!euSb*O>^~|)A>Dj=PQ8b$ch(bi0sq=a#|QG2s9`=rD&4e)`}hO zW=~6F#QCC+>I2`#lm~sZh))1s82{T0^R75Y;m-sK>-jI!wtqPwni06H# zJJBemUmQvjf5~f)cYe)zTnu@UAY;cD6vd}nb~XTBn{l*n@c1aizhR@5q1W_9f6(v%EBLt3y(^#V{uA*2VOp2joso!}tEbj;oEqh4)zAt0g!bN_%d+4YO%7j{!%~T8}nYJFU7C+BERo! z!nha>9ZZ+8jNCdoio)GP&BT%j9F&UMn_&he++8L>*q*K9&<1EtHuyAcVYvf z(sH-_(Ge@pSqrk3*52gvo5wc0W36}E_i8o{ev=ZJmz$gXMXm*pw(+VfQ@@{7&bv2c zQLa8TOw`F!@pW{3nhxB+i_Q0gDCaqe3?v|M>g>kUs_nRgw~vEZx8&jnjP8dzsQh?8 zN0USZ67Ss_r!_tkJ zsv_Sf>Mzfze-PNT`i?Cf5I$6G2!XX|8hAT3Oa3vt1_9uucr zBI5_+4JkC2N;cF!sGS;_Y>Kp5sK zYOScLGJm^d%Gx*HA`Z>m_KUCY$Kgu!u*$SJ^;> zZ~w2J(^}z*Xo26PK8ynttPPr41;}|#ev|1;O*;fUQmFL;QxyA_Fc1;QPXDPAl_6r@s2@fW>S+L&$ zqJOs)#yeVBHv(kiy+=Zq{<0Nn^5Hd7bb{p0`F94!rYK$Pur#?!v##yVLS>Tq=g+;Q zOdMG;PP|c6rji*U3;56`G~SnF(^5@kAcq*jw%?s893vu@#;$fF`5j4 z`zAO{T*Z#tNvD$MLyRtJh34EqF79uKNsQGf3VHU}LbYRP;-q(F3>COjHd@GMaQ4JSiao}J#B3kll|u)M`9iB%MefmN%Jy{SEry61HyHtM)012H%M zI2E`qJg}^kO((>WbO&v$(>BFqw)1)-eqEq;XbOomPi|QvUENEtZu=6 zvwCYy4cRKGY7o;W?pEHTJzuBMT^OQ_}J zc`rU5Y@4Q$s6@eMFvZR{)g+ z0SyJ8a@Q9Fg1K!6^)BBvl<2#j#BhELur23drKh7y8G~K{emOP3SdW7o6txYW12cmA zNw)23c6vr#p)TMdT;hfq`0UE6%Xhy*zUkOtUzjFco(0tQYBP7c zK;?_2P>?sGTI1_d%ERG9rVadoea!{O6_N=R)mFqY!HG1W2+oS$HN76$CWV{@7A*f&H6V&o7$`Vh1syJhm zBwOAmp>Jv9Y1Q(R%H$d=H+J;bD1CBI zhN7`%$ihf`cVCl;g62$*U6ZL$Bbe3dOKGvIWt0S->!Pb>B2a;=v zA7#T6o?kos*vLY}hDBKXjk+B^`3T^tRNZwQW;?#tSatDkD%>KndPv4V_!%SLwG^L- zCU31hO};s61OlJ%YhGkDe-z<~BdniC)+x*COi%yiEwKC{R^Xb-%NqV;@I3#kUB1?# z#gUFp4M*HizZEs6B(RL+htMk3av`(}ql`VDX$4~iQm@W)+3x==~*8EWnp1YeV zU|H?;0fnwtQwWFYwxG&xc?S777h9^dW*fG>RvHl>A5y|9DkzW(r6Qe%dDcRmdjU1= zj*kx)E+wU|)y6JEp&^Jb?<(Z(-X9^riP1g6#H z7F!eE5)s9A3(~~h6Zfxm{7gC(SX&{BWy+YA7j36h^IMapYSS+_rFAK95%)FJSD4^7 ztz)$dT-$uF%>g0SB`sPW^T}%FW9O$CV-e9Au4kRTf(o6(baK}BOiVH^pfqZaR?#k> z_hEXR6?*Do1tD{GS0pP%^exRZr6zUeTKw97HiXuVw*)=MkZ!wZ$`H*R7SYiYC&q%p~S41#bld{mBOG)KgcLnYf(e-o6;0 z&&>v6-|omf3z3KtF0xQCgXLU!QHYWk)Gb2wJt zO1dS#*;BID&=t=n|J*l+38If%^3ms-QL$0PUHw1z29joMk#ZS~T# z$2E0tu5Hb^5BvcKb+qLck^?xC*Gw2k9t`nL-m7W2)gUC?Dz4C|HkV*8FDZ6Dlj#!U zM%|Mmxb#AL)d26)iYV$y1T`mtOOGzRqmT%^t{)a*ba}qq~EP)*G}S=gd!?| zD{5t2F%e~!WHX8N%ut_MH7xUygkD_?VN9m)^ZFimY|gAPB2Y!FpA~nV6CY5m_}OrI z!Mg9ewX2)IeT{fz{>~LkeOvbsbl1oP@airE9yEvDtKqRy>N%fxJPWg3MWc8gx2CH9 zYu?tEd`Yu+i~XUUAC5$5CmP`0d%ayWIf?nWy`JgS`=$q2$)1dW1{b{t_KCVLbXlH{ zZ`A%TPq+W~_d`3o+r?G}US2XUtUIFg9aF*!-Od@X2huQm2b*VxeS_8?buG+jGI_%z zsWJ`tNOZIu*W`od+S*2Iiz1KYIOUXbZ`E@z;(wnt)-}0x)9Qc1`bxVdA`FMzX~X51 z;U|;)8a33HZn6xJ{0o##?q*Oues&6(sW_<@rgOgxdfqKj+s;Kc{t>2vxc(DiyA-N> z)4ID|v?l4n7~DZur#W1QF_D(0gH@}O=Epe-hpAzJZ56IPCa7d9Afwqor});9c|d?k z`Bm#Xv6ia=_M8tNvs)z7oXMtX(6I;8bfD2;B1BWaJ(lfhxwiZP%=3a`n2w*z3_VNC z!&pQiYtUjucJ$I@FPwq=B^)pt}hS?PloA&#QG`6hNsk>lb^eerjfY1s8? zPT%F&dyV2EtKnCc+Ec*i=(f4SgmJ@dbkn{R?eRDrAEA4z&rV;}I>UPYpV90CIz8Z5 zc1nk{C+x>UEj%3K2#L&`S0zTGt%>ekCmty}K1gMgdnwA5qVb#wmQDV&ujXH>2TMbi z{v;hBW2rR%Am8@Jp)74rb7*_nCp(eqin&^RdDUI;AQctgvz{LjHtYSLh9qG32k%{& zmIMdk4#gSnwK5_Ys18PCXR#Wfx6P*AjN@r*zisjJ3f|PKpz(if27MKFiHKZIingE7 zP^xsHii|pmJG70-JG3?=Cw|c9mGVmYaZ~}G4_cg(4}>SkNJw>N99ao`zYwu&Vq_y3EuKY@nwjsJ&nvM&*ZtfQ=1BTHqO zBwLbF2r*>|$(C&}W-K9l_M!+O>tq=_V^`MfjCF?W!weZyGv@Q${ht5t{Ql>E{^xwp zbDrZ2hvPnT&2?Y*b-kC@aysw%b`;BV`>Vq6a3|9Pld-#WdtZ?%^{2JE!prdCG?+7l zR{?QWqSo`yuHTcz9>E6gx>24sN2}W*S8rcDXQ;>AVjc+=Zj%RiU<%_@&(%+kqM_Y? ze|suMQy;duzb?lMpQ|qK;Yz&0P)rv9M7~t_(>k~yd=zMUQO~cUg`l-#w~651(7s8k z=5xii3EeU+GZDG??}_F2(hZwm7j1~vC4dOPRF2`V0B!wH+IdA`8vnCe+BgsmAcny& z4+DcgY&QmFAMK6e^EEV%{`WloTc!W!#Uk-P%0T|M{-4QqeH);|M_BOglKIbnDszYO zRWw;z=`4WP4lBU@W9@-}yrioefCZJ3-GP$s+^mAY@sAvjH6pUfr@C2mYCn+yRLR-uV)yFg$Q=KPnxVTSFVd^t6US$g`4%2qcmK%HSRut@MZomR(a!hy zg0dz0%I?$er@d@UyF|6jps63D5R$I-Yv!uGw}s`>O~*~hJ5mDRr~#e!ECyT^^S!e;15;7r7;Olzz0SI4^#nU0H5d-PGke!aCy zM*1;~uIZ2V28I#m)vQU+EB;v`aA@m$ukV1)!sx^J@f0O(9e26?;~vA{^`iqz|NOaG zEOS)A<{aSj=IyF6(cvs6Sex`MB(XAh08}hCd?RHI-rpZ~e_t!q&7~>l5lSMCqc#5) zi*)msp7NS~h?1<(IJ%{@&*JO@VurCD$fm~7A?NPe+$r~Z9q_G~h27jJRvs$|or z4p;4uTzs!@mq*1P|CKAc5bgJ82*~I^BLJmBw*w4q1$#d#Y!hmZ6{Ccr|IvhM#6j*d zqOeRhRz{bn=E*|ntGg7R!s70~Deq|#+XOwSscjLr2~!ECI}e-R=g~1VtDiQE8`W_+ z0bE)snLYpfZ>4*Jz{8BC{;Ul7)t*91ZkrR%f7q<+znXf|*6{qC&WseY@M~ecr2HY6 z##I6=Rk$rsEu=4@hW>o@5N5KcK&-)=je}hNh~w?scf-S zz%%gMzw*GKismzi5j@AI42Z5Wz#rF*jHGCJtS}r8&7?b!U*pTR3XO)$zKqF?CM>>H z5{XLhUhFN3?mT^2_|eJLXM>$84@^W4di@IwSZd$AY5>>uSGy+`@n!NAya=A()+DTf zQJwsI>bd`a6_BmMYPrE~FDziScJBX6L-+s0TmMH__ka2u{aO6C_S&5Dk~M$6|LD-b zY^Nrc?HM`E&2EhKgX!m}N@Of9iEFbLGmwC)vXE>eb0eHNF}bBHCft7;ni@@Pb*cO;plH~@d7qsT_uF!JRTIYR zmy>KN?BzS~cQNxGev?Cf0&mhi?R9fQ{Lzc!MDSi2(RP+}V@4gx@k40WL132mz;>d| zN=CkPS9M|D(Bb+o`Y9EtE2nCo@Jnpd*iVcE*_%W<>PaKsBN2T60I42d$Re{dk$)S5JO4ebnGZP45#Qg)Ro6V_Qf74)YxzP+S{+#GBrc$OodrY zqNidE9>u6gcLsh;ebRC9w5rXvZn=t20d(kCdYw)to7sR*@ty8yAK4i{hmt3ue2l>d27^u>$Ugth98No?pY z`c;-ZO5ARKUdrNDruX%SjlUh`!T5IA~rm&nwIjP}yp z%H#LuE&Ne-d)K#NZ{cE4x1?Sas?kd(O(U|Tp&m_^RgRgEFGGzm^ycX1yQ;*5S+N#P z)swk=7}M

hJtDj@MgVjY|AxNw`pc*S{t;;R%OpoGH3H%MV5C9lBp_ zGBt8%1bg@7U))*YLVe$__q?q>wYQ&Rn0|jsBE_`kLzm>zidnAB7eTDm6}G4JN~;FY z|LAt5Eu;D-vUoE}$|xP<%`ej)!YT|DHSP{>ql8}uB3|GsEOM%(#5{v7Y&`;kCLujY z{UxvmGC5Ce_BSYgg)3lA0o#&qQ4`xu%CRi&c_~z!`fhYTocTya$gr~KWrd(XHGfCI zq_?7WFz(5#J3%3t>qNPR56%udYY$-SMp}1F_;)W|kGME=Ehace!&^mnoT%Jw{x(mN z&=9)@p(9sxn-RJ8m*sWf$BJpDqPCo8-n&|>ypLdE{R&dJTZSpxqR9Jsx?FfAG3-_j zLt)xDeYCh=?w55x`MAI**A!nHZ(=hjclW{TjKcIs9tWKk5cG+&n^WX;?CB4 z8pNxaXR5B;0z#zJESLJL7opg1yG!=Rp1_wkO;~?}_~gUv*PW>)nwT+==l$mLL8c&9 z+rZ55%hXWdP;vsGOFalq&*QWDkS2|~#qAkEidC7*g{`Bmb0V#L4+6QK%8PT_xl`EN z1ZeD60P#=4Sf_lLDamg_jr$yw)?*#%Uf=$_(9ZMrfOFxKBgs^{g~U6Kcf2#Ot>?H0 zk>tzSF^K2rM9>8||8}D;JCVC5A7hH85=Nw|8!PNl=s0ydD9zo;M$#>+vfSl~#_gRQ zI$kEpzeqgL<74_pZYC-NB=T57_UcMv2R#+oI=X+z)U?miq<4th|IsaE|1?=2<5avP zpDG(*`B?jo{rZE!B9Q)R9Qvg>S$30p0lB);4@!VA!vqN6dt}Qpo0lPZ#V)~|jUD11 zrf>HxE|;mLnPeqi&StOqk_OwqWjk_Oj95*F@PS|ClVt&4v3Onn(|m0PpbUJ2To&eA zshe5-bip9FVEO5=m(;@My9aT34+QxQ+~)N?pIxQjMV|qb9CDF7h%RYuK&QRWfp5Z@ z98I7+N$FADw)G!w39JYOXOldlJx48gMdv#MWxj`19Vy)eI<7L-ePau2YmJF;Ve%19 zy|i1{Jc_2(aI*?ujA|Vych9m|Xq(w9L;JXMXIv0?SN7Q+YLU zED^3`9l|Urn4E+=3s>~JOE`!fzCDOlgcY4x8an^I#OA`H^j>&$(O~#Ru>=}h1yP2y zMVLVn8nNcRDUpEOH5w@KGtLiu+3irswrp%_X{l>yaI5My;TyV=`QZ}hPBrVI&xh}H z#9ec;yfQwUa-TK}5dq{Bc?jU}05c=(SRzXPB5HT8?X7>65{4A&mgTMCnPz?|>FO;> zJj^KEMg#F`jI1yVfT_GZkU*ad8=IiZLRD)f=%!y-mL{h3Cf(N()jg!@aSVBLsK_=2 zN3u>!2DqctNukUTR^o)#g0+k>%G@I_rF zyL$m)R)af6#Jr9jDH5oGy~yW!>XGfh z)<)(Xe_3=t=G9N;j4fL&f&BY>>|G+ z1TBZe=hvRy$iXVGs@FHhDL=N>h2dzDvTPmZuFG}640_EYXa`NG4KTAa&$i0CZhOP2rY7V2$oYFe-vC_-(p}uG;)L00 zP-7{T_hc=*A5E4sO-WLd|)o*QWUYuH5@* zpdBZCFZ#hZjZx%zaUZFq9==Y*#0n;5Z8@K!NE;QqPEIPA3}zbB~W+`%E_)Xy(5FX1;MC2|{BpC4kq8PXH0ngVmt$de#>;9Y3|@5vBc@d%g=# z%k3Q`UzG%Jrft#}S^K0Ge!oy{zT=9|rD&JpohUL?ee$c>hjTyH%$RpgBe!Q?+m1;( z>&tT$TbU)^S`YV)8li)W!{@muJ znmai0`+GrS-5QQ*z5vKuv6}j`TO1#+Hv$o|n?)AzMv$hiFfX zaiL}2DoQj@Kf4)$rij*@$2&=NDR$fnH?#q{i(5Cq7eNHWv3P(V2BDt6MtVBwuPAfJ z8~B~B9$L^ws73~OOO+IF=)Y7_9Bxf&{gyUyDaKd0!dNp|mA-<)0(?kEnp_|7B(f3E zohb=4MY15#VgSb!wdgK(h3rxxbzGh6Uo#@!_~~7=Wi~|2!c6yeqrEmEZ!a~YT66rfB9YLh!*bHG>?jd3$|vieqW0*oj6cU z8KtQ|dVfzb7s@^^2zJqF> zw8LK;x$J1vlGO2FRr5y|i{K6%7;%)f_p#YE8ru!x{Z0LJlUUnE;%JppzVz28PX>Hi zbl*E(%e>U~85^`3)p%W^Dohc$pI!jlQye}B;E&qnvFMaG@M+YVsW(35+I>m@YGy?j z7nE@F?K@F)a0d#%HhKe@28drF$daW4XjViQ6HN=oF;jmXCP=*2pLZqQYC=(JM)8H| z!s8TUC&3(yjfAlhG@!Fl~0DadA z^0m&E^^lmVqR)&igHEp<#pRjOVc$XYNAI|2AOuSUSx%-e{|%@?peKL6krYNai48E1 z7t^h?H}o`m%7phP+mx`E7i4%nZpg8AUF75QNgNs2g!FQgCGzp=#37O#AtmCs_q;iF zHaq|sTNl$Y`^b9Ii71owD{Rp8{zQ1q<>nw07t9lx?4(wfrF_73Z|>1rKfu0X;sG?E zEa77<%-L(+=b*}FLibAnto7_t zB^?g%PBg&)5-xUyP#V=H?7rq5$1m?x=zetpRTA~3oZr1vCHV9hLb8ZxZBtE*WL zli~QT_$wb}P2~y2mEr*i5I`!(hY35CZigU%hbQuz;1>d|IFvDK7I*K@%<}8HPG_l0 zayHajhAYoMhO^MVg9$eI3Cm|s+dDv15E;E@9uDTO2gl2Pn+K|joX2ku`lF$gnd{PS z-|hRR`7+=6%GDZo*}POHhqwaP2FAc(Z*|;&(UX-6sPZ02`kh8kmr9QZX6)N7VO3ob zQPjQK5&0oHmKSu^T0W-IC;znjNC3ID3_cB~Zz2Zm2@}j$mt(Q>&TTVo_U=*{v+ouM zJmu|l5`709G&D`Q%nxpt9K7ePasYw=Al&#D@r(h%i{%O?7iXQ&rhJ;dn*Vlo(hR#~ zM7cY$MlldN{>3sFZ|g?&B6CBe08zGgL_V zLUD^bQKzG{_-()i<5B~qzR#LGe*{xM{J0d(uK&-g?-`gZ3F-6HA_bq;(LmHY<4y!~ zs?zSPu24t%LsWb6!`%L4S37rSXM^BN3e|jXWf;WQx-Xw5qP>xPz;IQ7fJN8)jqZg; z!a&T1&9j-3Sv>E7eT2GHTk^!>2Wl;B@4MtXL&k@U*IzTxg>`B6t;B->faq%6O5G@m zP}&P(gDF>OM#2=`gUhJ5(fmU8Nk!W#ULRcCc^J#PSgp7OgRmxaAQ@nZ08X3s9ZKXQ z8oTyN-DaOMFHDeJ>{01%v2)WqL01IAQNikx%y1)R85rs9y>pUau3g4*0B|{5bRL$l2Qu8ET z&RB9juArko8KCk>tZDX+%}_@+q9`6*t4h!H7v0FRM)X- z$$ti??t0xc`i2+VsntxqhdHm?Nl0LU_t{@bRAoT8>ATR4wE(&AorK3_;!#sVG#GmI z9r~LaQIpC)X-9Z!`;%zi15ieKg{oUi@74v+bo<`ydBGR)$K}XE@Cj$qjMt4(W0nIT zR@+jqK+wDSlCwhPkvs#Xoh_9!z89-DT6vHk#|soJIdkL7B*Sn0*oC}B1JU{%ur7d= z|MUO@SsNHo#+(r~?Y9bSX$yG~SBP-8x3Qb=lX<_@S^)jU#wpKk7!fY@#g%hFol*=* z&O3LSfbc+)C6Iu-<+UfLc>w=AXicCyC0<~~ODJr#Ktw?3Ij^atN=w`L%^w%D9xq24 zFKJ|9D3-T$(@#riLS#Q0>%X(6hK$1H&!60>oAj9>vH%v>C-`KZBH11X!D!(I0H+cxa(>p7V6a^CW$ww)#c5Q+S1Yje zy}G*cr`I!-EFUu`Vq*_LC8y;beDJCK#seyk9WFK+h9Io1QlDyhy?1q^*Sq8~2N$CC*v|GUlO?!f2`v3`FlC(GSga4(>*S&*8IyP3tr#ezuM6mZ!LcQcPy2UgT}T(tmp=zT=z~Ww`gO! zXMa-lD`1xhr};5n+7g^U=NB-Th5BXN+tpi~(pqH&mxN!-70`tn(-9$a$`8oRUlCnH zIoVX@vzk3ELA@Rb<{_p(KTSn74%NOzJkJyD{iyxilkOnL0;c`4zu}r3^}*((t%~VgZt&mxd3_e#iu)mizYakDamgQsUtOGSrStCWNtrY z)T?bU!Rlavf%J!H-Iu}#AJG*d`?>-J0EI(2r5eE;fG^4OImrG?$4u(l+on5*(|6)q z=f6z@^TMcinuqZ4CQAdjTO~LfG#1Mh?MtY$Bw8*vmqX_RDuNEr%@45G^Hg>%IeU1( zuB3$gNoh9ZTG1u@Qg~_e2!ctw-Lk?lMC-0g^32LS`MY$hnf zu4YNKHO9L=WtJ>^-R#+#0_0CrkrP1j-<7H{yO#n~(?K+oO;QH1kz^o{|Ixj(UI5@! z4T!I&Cx26}omwA&TaO$&fS5lLK;U-ippazgl|4<5RrCdW+eK)d!}PwkQI&y*Os=rIKEPS&POghAaR~ZV@8HG;K=;qKo2HBhEmARGejAxtR>-e2pWY4 zc~Gt-?0MF6*V8CN6yJvVP3j+Br(b8 zh4ZMYmIVXPlHm`AL4O*b8mqEiWLN1okD!f!8xQc&6hIqMxr2vlJ^@oEY#fmg<6d?q z$)f>5)84qyh49JIt~&O%WoO?Oe_b2d!PhHBG$6|{^-cycB36MSNxf#5PG}dghj_kU zSA0szU-71nL;szr)lf4H*^R+=Y085wm0QaJXVnpk@LZI{<;8K`rW12S&vfl<+ zO?fKXj?N73-a946yIYvB#m^Wm@HYSPjWlMd0d|``6(CupaRGd;o2J+>H2ZLB`m3r=F3C{P#e!s}GbkKuXz)cv0I-y=j0Q8^AO5sBx;2s?yx#m!Y zcPdfua6I+kba)&WRU_X5xwK?yX{9bF`a?Y54EWq{fY1G)Lkm8_uqxAK&PMj`YRSf5 zA;P=ok8N_Ua3vIuT;jPJ!TILfQQOM0R_2}X8mr|qWJu@OJcKj?T(1NF3AnG;XLLJT=Uw5OmSGe3&K$l~8GgpR9MN?b@eB!^shjcO z7k~hW1zkkSzQO_}bKM(uaC&;cM@$M{g&YQ*_ zKwRkp6CR>Yvk~s6pe{TPea^kA92(?6z|`vbHn1saH!|YgFxOq#7anFA$G@XYA-65z zPiTEBHQe~K6iYxfe-HdJ1tZ)QBGXMlFmInNAtQRS8h zvsI!kiX@J2hW{_3*gvuu@VWu|pEzfQXgBUZ;xr8)RcAV(rT>5cb>S```COkx9Z-b4 zz`xT*JCv^iF!p?)o9nOxk8Olnj3T&v;}tq)D|hJ za~cHZ&T{B`|4H;&5qyR{Apw)2_^{|LKSwjW|M5@ zN4_o_cza(+AJck^i&s}TRJqPhx1Q#6RsMuRV+{ZftKM9a6xqyo4J>H$1;GpR^t_nG z49UM;b3LH`h9Ga9r@PUuhXQ0a@60tW@zP9}!%zRw1xro|*mZQIE>W`|ZIjI}m3>$% z;(+ff-q6j$?~ot}eY|pfKKCoQ67?!k=FPF$%6y0H#J+!SJ3917QydN!iuZ#I2KB8( z|8497#ltTX;O%UUGukR&$4VTebs?%UvUmTM_3LfvG5UD?9Y+o#$k&0QM{p?dGMnyA zGK-lXq4;j^<#ld>60$kl3Mg9`Xl(O*p zva}EcucUo0y|1=f)@38*vK1MhyuYu-VIz}c5OeXt4~b^X*U!#rp>*=Ufb&w$E8*){ zSO9Qv$I^6(GBLmB?^z3t6sP>XnL2es#}lcUd0jigCM(R`06I3ny#N7v8w~KXP^bRc zzq;*yrO>Z^BoF^&wdOY_Y#(5Ku5_YBjAw!?^J5T=u2h8-G4~J0YZ4tE`jy#ukz@ufnMu}OH9{a$L08a!4!9Aq{$X@GUL09 za>CQ-ew^^t5JE|N1mpmSm7H^=KZ)w9B3n(EwvMpxT!{ZOO+S{U$>(Lb{5kTLxU)0E zf*Vrn^aI*mhnrMPeho7!=n3+;U~OSD<{8j98fnnA=`O)!^TcntX-R-|LRg8@)*=eq zO#Tjgy=z4F=~ME4Z1C&l+4k&nFhHm+Fl$qUZ&a0uE)>PL0T(+hrz)4hM*&@`<0~(I zQnmaz%o4tF4#f#w&3*7`c({q)?_)f%)tf92j#+q78U ze(yx`9igAOD`kL z1va2Ws1r`P*8{@l(Zc<-smt<9@klDTe=!lIpf!QdpfE2PJ)c}hC`ZoFV0lD6FizX-u zgo?Fx!Af|TWxFgxOX24!)I~MVo4CUr%SgnIJ*c_vO%Ab$cy7m!K>Cm>yupo=%KP^FfhJi@V#N^n56)2+j zCaD0&nq&9mchiKSBgkv1I6}ev7JYRnb_{5VcIhe+j~inGMr8WLSf;%{IW-9VdKYuU zCYjHK^KKe+h_2%|{clUY^?XspD+DKP401tR8BojC4-+IB{cT-mBYCK_NxMizaHOf; zx-K#cAvUgwr4YEWS!K8?-*vmQN>}@@e{*RHykw!q7;f`EVwRz-lkp$&6k0WlZ^cp| z^STSTHTRoc<6h1xRqqdJQ>-l>UT{xzu*vRE+J%EeP{U?y2M3jxfJjCu%0FqI)OOCA zuL=#E3= z2LQ#k`~~G6blv^3Vt0X?@ERO9uSY`1kjVs=*ZlIPn^StH0_#P_G%gm#L~mfTxC1%& zs_l{JK+l2LRto@3nwi?1tgh4;dL7a5c8xJcRfUE9v8#L6bv60}HS0m3gV<&1AL`B9 zQ}}~0i@Ouj*BzYtL_Lz@gEiy+laqZ3_5pGKM_V4XKrJ@Z9n1;mCR_JmkZ)l2-axG6 zvN-e3{k7efpqT^VTG-aGE@`n^)%f}DEwN(*7@lxKV>iVr^HB{asmj$*L?4ZP3gyl( zuQ9WChv(j&Px*b^;FEXs@{KZS^3yb3syBd$b@h&JNl8%e(#{Hj_nzyd&%qfzD}T$z zAo=ll^-Smk78e7c@3l8gG2B9+xO*&#O0l9NZSsCnUr4iBuwD1_ENAOe2Q!~XjYs>S zLFm=bd3B4etWKz6$E23!Ua$L)4-1WuM_c zd!C{iC$#lG@8$`|j-e+}!WJn)M~PSeAeb@BbBxmf2XmIxNI1ar5wfj_ne_r8Tq%W1 zBOd;L(+%^U#9#as%w{DnYs2zr?k7w6ID`#`I7L^%uX;2nVjIYxT!9F>Q8%y-f!&qx zP^VpsAQi}}7$gUNvzWgzz!;{#ip02fJ179XQf?3wzO!rj^Y>Ye&=1%biot@7Z@v?L zw8tF(uP7y7xx6L<`~N^JxQvj z$IN$iiRzs>*&Co3Z6i=JJ+VZM6sNE5q?%9=tz!`h^K z*|K8lR8pGGr#fDGRpZLr(cfi$3eo*FjQ^9}3n4u66fh=7-~jt>GWS5vud!(F(wg?{ z`}HPkk1nJL!a;Ihiq6@Ia94%MbdD`>cZ2Jddy+hQ9YC6EG@h;bV-Ak5Sy~mZB(8tw zd>q5NVC=}neVdg|tjYko5(P-5iL&$~>cq;3cUSIC?d^+wn5S;N96 zNIlc~;N11aYa?_)zpbOF_NT219lY~fCrPUa5f9tIsscTv`hLE1El?^H_p7yDLfKeq zYF?*{$|(l8pzLQ$sc>RoddW7NfjHfsS0&M(Q%Sn}%2BrR{x;v+onrVGcDld(@j!Nk zF(;7#8VHbF8WWZqz02&EUp`dqt+ka?eR5~?f%b=Ip(@>xKJ7OIe;>r5c@+SgAe9+y zOJ9-3EFty8?ES1hn-T;2l}nNgsgoZcuoxJ?ihyPVlQW^mE$eUJY&h)_X6t-MSJ`Q# zMgyhqI^yb;o-;=W%AWt4lNi&v*|5skNZpKF^q8e7YTt;T{D*f zK(UU-k-;IxE_5|wJ?VsQ7xh-0PyDZQNIV_s?*Aui`Cl2#Avy(A>m3URlamLY%t%dr zL67fkG<*wR`rg30&wU_v!A0?7c&F;Y1HM|if%1n>n}+!~?UkM4;xqgML*2_MAF70= zubAfL*6B*e(%7z*Jt0dEn5PU}U4-aYRQ)xckrvibH&->`YpJnu4NFyWzI0n@(dUal zx_+QD>>4&X8jI)dq4A~n=VZrK-;prOTY07VHyojkGBez`TKiof^o5Ks&&o%-D3I+# z$Tiv*myE5^EPm}|6Zu_fp!0X$!!n@iuf1BD4jA8ducX-WNGiw^^#=jb^gBWtOlh0R zL>~X~59L6fgTBCQs{t24)5f|<>A_h;(h&2By-#OyodcDI$qx+iU!m{>EOS)_bOWX?ShV`1FTKv<}(RU5sWC?4dL<&<1$E_tlJIlda)?c;q8 zlT=?fUK%hItflBsebiCz{^|VKg>;7z51PII>?C#z2kRnIXlwec6B2`D z9VVTGI4#=NZR$GS0sRn*7LtB6o9u_^1QG5lUM+sJd~?=I+r;-pE>nAkr_(F?ozMI3 zJly`#g^5QQ$P!tk$Lak+owgdWJ_lPAQjv1Fl1;ddZcxb^d9)c4uzRxaLN@AZF%Q=U zdwP+VVAbL+U01%?K?ydj^V()uO78TweHU@%Mc z@CJAeL}TkAw9?ob2{V))#96i0?}!E&yHAfEO?fem7L?ySBXQ)+UV&n``q;qIe)!gq ziZ1d0b~*ar%!B_YKVO6YCx<0Va+E%b2;kC7AO@ra(Ywp0c5hEIk0b)*hBCzyBMtQ! zt#1&VR?VXkt}7giZXMhc=vuXfiiY+qQ2mH+%Zod=7m5R)XC^Vz4~X7SKbos7LEXc} zJm+t|((UVEw`_%~N7c_h8ZSgxBc&KL_e#&deiRS$1_YJCpb8XM$ZSfQP5t|2B@h2gv-Y>V@v zeQ&rI6RN-NDGZ63&sXa@@rN3T%cu7ghO=zs_bZcS%m>WFTM}nzGGyyDQ-kOR)O5$k zlbKh3$x7lYJflX22aqXyzQ5AuI4jqv$YOiB8pue1GL_By$m~x(LoPHZ+i_!O+$%i{ zm9j|sqt=$Pjs(zqxTF_E&poOpa_OnbiDRsJj*<6gj-0>eNr6DFpavfY)d!Y(H6XYH z%sIAeQ#W41Ws>T1p&7r!(ztIL{6|;qi+*^F+`b)uT;rhN_>1HDmZ5U17bLnc7z*L| zR+G6qlacM5fe^04LCDgZR37_X*?<+nmE?$mht}B^sRQQnQUsL;&VytH{+wH z*%JG27|SRxU;4PEPV+SN8x^0Ej6{I?oRe}D>I{&)#97(D3iGL&!JSjPYP29#*R z_rdCj)glPN+ZE_7xS72Kcz0k{Z(!45gq9O?LPx}~>|1{mV`J-1rxk~n=WJT`pAwjoFP3JXlv{Q8_xi~%0DZTBULU8%Y2^B}#LkVhbsr!y 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 literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/GADM9107165I0-tax-certificate-1767252858.pdf b/CSF_ejemplos/GADM9107165I0-tax-certificate-1767252858.pdf new file mode 100644 index 0000000000000000000000000000000000000000..20d79d8da537c111882775ca3cc94f5b58837172 GIT binary patch literal 138169 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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%0AfN!&n z^IO@Qp>+7X9R&fDE8k02USo14+q1 zz?{En8^~}1`>20O;g;^-wrT&0DenLQ{~F36@4yNEHBW#W3jU47EbqVt{xy#PoE!Yx zXe95z1OA~k31l7!;06Cs$^$&N>HM*bf8^zF6#P;*x7h`r`+cwYEtC@gNSL@;{2&ET znY8z`aJMltVUuvOHwS{G9f6(WHjY+cV6(WRxTBlRZ-AeYyCvw~Nba9c3HrOvr~&ww zY6P~y>*<4`Jb>y34;L>xmjEZ&(Zk;Uwt{{W06>3NUHlgwg1K5)VxdF8P$+Q!{Q~py z@Nn^f9e;swa`Ex;0Xe$8{s7~L0_E`&OaKD3)SqGeKqdbXcI&>a-#^0yxCDNR55f!V zyZZ@`6AFd${+tGsOMn+>mVbC7e}+N1AV6dP3C7L)cQ9W5pXmk48N%~Z8E|s(a|!%ZegKS{=MQ;tcQvuG zw{XQm|6w#i!^X$rM|E{QkfdE@q>FA_YfThd-0?p+@ AX8-^I literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/JISJ870518SD7-tax-certificate-1767252856.pdf b/CSF_ejemplos/JISJ870518SD7-tax-certificate-1767252856.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3b41e6ecae8b3a011a49fb2bb0a1cc2096e85e45 GIT binary patch literal 139208 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y z*E4iqP}jVco5wEhI`j~L0>u)sm6U>x0qpgz5nmK=&Mk!Mq}x0R58l9qrE=Y%%uCcD zcU0p~h+>MfJ}6Grg#C34B`swG`dC|`usCEcX4d6G{_-MP;t=peWWxi@k8L*)Yd{jX zFJA~*e<0{#RqPtolT~hK(&!s`w&&Z~*Lpj-R@#x=@6W>X^x5rh*YsD2_=cC4UoB_3 z#Zqiy0Qdap3LPG8Z0I-t@Y_yms{xV=RKhZy=kB#+*L3S=281>O4pF9`0k5+4yaJZH&*!NFd)M>6I}}!6ey5Q%0dk#1O^P{sM*Wc_!H_jk zwa+IvgV)$(hNy0GIE8m}wJ#b_Ah318}4v298Pf;<|tc}1OT?MnR)QI*J zA^9y!l@EB6!migxe8}Lvcp1-_jxObE23G5&-B=Q+UZ6K69y2of$*I~tWiRfxslVHL zJ?FT_iPbvyqFhv(&6^Kh3}BmWG#gB_h-xyW{oJEyz6a-NwP?GvBUd__CoVyMar5FC z0CI8=@qiq(ij0OytSy=L2|2_%mF{9XpN9pX$q23Lz2;4~n%2qd`|)ThhmL$6m)$G+ zh{iTeEX57P_`iRVVocn~C5h+&7?_8 zC|%-tdGc3($2QNJQ%OoT5afR=4nSuizvzlnIS7b8dsnk5Z6hpR^GW;o=&Oz96hFNq z=Cvsoy{_-n5l?7ix2<=Iwq|hAF2B!?(2D|4rmwf`;N6bc!_VAfh-5LrY4tI(WPXoU z#l>GaVZr%4gD*4~c3 zUg5=*3+*0PAYS-2*Hw?1HWk9j9fEh%CBpuP;(5$gAhBL$?#&Xbp=2%o#&@Y?z2oXL zV%6-IfnWGBc7tN3!a4RUX8NNlwC908dpkR+_k>2m@ET8|Nq$`Mg<}N6#0%ZP-tU zwE=dZpi#zEk=YrV68RbyAqGv*pGGBd_5Vlrkg!;xk*yscWOLoc=MhVy>J`ApFg_ax z={D!2I{ie{X+JD<|NASg;dkw8lM=ZIi@-40ki)RvbUPu4 zxHgy5iVluX(w5D3k9{sE=e$YG#>_F|d3b&M;Rn?$SnIu3ARb>$E|S`erpgy@Yt~+Q z^!lsw$16L9Mhb)6KCH~GJ9Okwk}WwKI~J4Qh#Zhl;o1GG6mhn1y^J#_>9-%)5}u8u#IGxoV=T;S)+D?&*<_QByE|N4p2nU)%-QYAXHtX-6jsiPNs{AU zs|`WLnTnyRg%3WzHq9~)AGmDz8~J|h?J;N`f=dCC5@CJ9LEkd_1PtuX6;VC%b>UTl z)l%D#_<)U|tu-dJ|NS!f6N}&{0}3n}#f9_ByBN9Xnzju7w5c=08b=XB=Rdk0 z@_7S=7cKyWl_!{MK;-}6QL*h;+q$r*wEn_E(0q#L7U(i zN?VY5u-cFan*fHdt$Xiw^uP_7idt2M@aYL8)Uj zF)> zup6fz;2^5x^uhze${T+jnFlU%N{$W8&MaT8U)4q}p1F1lBneb@^mWfah1r}I0acru ze*sPosp^Y~i`0gyHVpwa@@+d~WO2>GuX=>AolhH$1+IGnX$%{->+AT9`eQW5 zHcetYK=kI{QH}&fTb73Qxa?oeNmE7gPHF6Ex*7BYD}ePUYc_${-A4Z=DuNmZs70l# z-lrkkh`+DYNgvU5K&w`oY!8aG>uCvvTJSIDJb$!)<%qMn-;ee!Yj5q zFXW&ng#G-lZXrVbS^Td7_4c=Ghd*VIDXx36PIa*liG7Un_RK<3Q{{ClJxITMW%L zs+p-j&ba^WX~{1e=3CDfMR--Uc(tYx7h!w^WXhgesJ*Lx&6MJAHhy~@DGP%lS^B$2 zbM${6u#Dw5DtE82KxfxnW7IDZZ_i?py;T}LfuGce_F_VxOXj{_|Ek~qg<(S~;yJ4I z1<>n8X(k`1V63gk&v8|uJv5!hnp%(dD7=KzYy>v_j1PU4gp` z-8yq>+-sI+_nGKm(?k&Mk<=DE?p3FYh1eDQ2E9`}_iCGU6N7mt&mlzeg?$k0_ zl$)$NuevsPY*(xRMrl(WQuE}&F#b{V3+{y6&@h9HKR^2|r@r3k*Z96xN)$@Rv2>fC zr9P$sJoz^XMs?HZT2}!pI88CU^FGm+#Gn405CtSE&dwWfinQbndd(Pn|Sp|!T3)x65^S~(P z6@PV8{eubU4Uba3{4rpNO73hDA!s?F)gHg4jN&))n{*LyQ14Z@CjkLCaOO-~El z6#UEy;r!Rm=B6pW%u}9#$ydAIY^W)j``~Rm$thzNtzQ}LX!#WIOJ!1E5#%E_?u}rR z9`ysdZ2!^4&)igdWDdt14NJV`JrHp({UJ*iFziMR+J*#BRmo>p528ZUNAs}=uI(0` zs@1;ENO0cj>#O^(@rfUuKRV;8fPXT@qN74MF_7rrp*jNQO-&wU;tdvj#d774v|zb- zF#Ro%4!my#mEE1s2LDb2)wL0&qY9q71?t`}DqXr)^HjStUu8zQUFp!&g~2tDi&Z>W zdC#|K$&?~Xn}Ca>YI5%r^4d#xSPUX&Uw$_AsKpxHV@kX5{reyfRl1{+sJy2`06az| z(yh=0r8ueIV;jcfMYq#5Eo^uqc|+IzR@7I1teOb|V6&M1Y$vfO^`g1*@cb0Itjj3Gg#8M&M{4;AR?9$5)~~Cfm$PYNTve#-Ke!K#<@&M;@D@ zC0y?kycCF4!T-_yC|yL%h3x)EXV3$=1J%NM$HM4Wkz^yaFtNdb+Y6U-FWl?C^Cp{7 zWL@LF-foVrFjawY(#_4Sk&gW&J!?2=`=UvMwE9N%DqW-G;q8>ca8aT)uq(xZ_XL0@ za$!yf@csYyeICB#Key9x9BZ}jG5gVho&E}_{;F~m zl|cg~oq(l?UL~glk;-#5^`m-|*R_2c2K>igh1(^cwgdG1&mXz*iVZ!i4L>Q9zIK#vX;OHK!p zizEWPpmYmavf!@y(?CrnD^1PVA=K~ciyxTL$45V#Mb^|O;Y;}_%?P;U|JB}GfYsF` z4THG5Yq$^`g1fsDAduh$x8PjdAxLlwZowhAhakZng1dWg4hHi!t&nMroJ?W0sLk*^CmZ5j* zZX%j8z-(+yVaNf|{o!3W0lgp;qi|(7pH5V!b#Y)^c<(u@M>jW5W_HZ3q|lIY*fhRk zpu2*>)bRn%erVSoPw&&^S*njyH78`;1P*nUUl~TwyiJa(fXHlb2|0laFk4H9yON)H zvi@=Wg2C;HjegwW5O$He_`X`q*D1KfWAS!(?he@|(pT?hwZ9s!>QatJVw9B7^QI z80NjvzT0K4l>;x@?U`dLG=7QUN-WtpdSn!hNgH8kOP=&dbPkE+RExU2LI8b5mu$5> z@Bpj}uT(#F{vOnhY>sbbe`q)q^`2E&Rp+*Te{;oyzZ^>uJqSFLd*aUbib^f&0ApYC zds`Ywg3$%dl&`QLqmTgb{x+KL*d2nNk{#M&B6* zne_Ct#!9-o9C#e}lf4E!MEk&)bKRu3RN}VeSvxRWM;bJ+-{!B>m=!7Xi^YZeqQT33 zqn=-hdR;1np9w3SwY%P4QWC5{m&7tgs04qPg0<)>Fexa12|j*WdAu~zj;}{r(QrKU zy2Ys_lX@!%A2x)ZJ?08{0R(Vth0mu=-kWiG7Em`9boiik*n6y@KSFq3S{mInVoji0 zL%YpP-FArC<+}A|JvD097TCBkhhu*dq5~e2se!>gTzOjxpOBbE-NCgF*+4H%PAdC4 z!{OD@`zZem+>c)a4c0!?@DqAU=6!lnae+Cwh zvbZ&CiV>~x1d|30CRK-$Py`e~1QaPR#4OYedx{{yirN_Sb0|G2gM9rc$H!L;8l7Bq z_KX`m6L@Fh^bo`{}chjGA0&g=HQ1EAR=Y~c2qWT5Vy6qv$Zj? z0fTq|pt!A-t%Itafe}!#go(3-k;yY@F;pZWAylMa+UlBi5F_eLDvAK@crVY-Z`-|E z1fw|OqBdZIA?E27h#`hR{`wC;0pE955rx8#NjD`~gQQ$(#d`>^!W(h^HhPku=@8#`g=Y91cXEG$xpeg~uN7X-m(-*PGjfv3l9<8y&g%I&PECd*BT&7?iAtl9k^Z4E;!4sd)eu!*HdSkKa*MVG=e`hYu9Ez5*IV+N?2bnJN_t6%z8d#+2LKU`iQ#$dt%|ZywcoStLWy&^M zl4!_n(`#M+b8kCv`xW2!8j!o9jTnqOvg?_Ii78({iw`SPjNj=N%u#E8694IKb97dm zBr#Q^{Rr)ITVyIb6K2XC<58(u(mX%Q_(ehCuCUXpNNlTF6BrzdMsi6=ON_=#seyWj{thofQ*{G}RX@+; zG}CH>h}W}FL=Qi#;L(aldoVRfu1eeQOlGmu9c){H+88$cKj<@sGG`!7LkJ$2RDRH3JsrYhkg z;?3Hwx{4;YH$0L_lB`gu7dER-oNt?V*FnKceA`&!qwg?7^{Zcn>KykFNmk5qN-}*l zilCU;Pl4(R(`(x8N^V-ufVOr=>l&$+ay!ZQ7_DVm_2&`{Ufb5pX>uKJ-qA|56HRT& z48(|M$_cY=Lv_k{kIK{sC^={++^+tTt#kW$r!wEM~m@snObgsrbHw7uzB+KA^0E0Du7?0zkHT2yqA~NXUo3pD$=w=!Xjq1_l}y9u6M<;fsKXjEI1M zga8kZgo=cOi~?Nnh-m1jC};4l2CIZA1)B!Xk1q2i( zBs3=EeJ2DN(0o{c$Ok%qz96BXVPN5aCLtjM2(=hM^P!=kfX2eY07OB00qGDhn6Oyn ztfFw(DhBWr_Bd>QG4Bu_i&eJaJ{vuzWH)s1M?}KICmYCcR`i2jU?H!$6-95d1{bS=3lT*_(vvaF!>l>R}+dI2oPEOCxFD|dXUf(>> z1qlKD6Is7f_6NE!0lJ`oK7fILpbHYp1^7Z^!oZTV!eNQ3z#G_OQ?U6V;E2V%t87Dj z%>L{c*U(`U36GLvmFnbyv>%lHbAKmg`( zS0Y{`oWF$|1Z9P-t0m8 zpSmgXAauTx)Pdh)B3=bxslWRw0}8W05vcO_W8$xIMF{~uRm=+2`X@$+KieM+6vNN{ z)$0C}@fFw$aUgDJ^RJUgg}Z%80onTFJjgdRn_2KGBE0|bef@tsLx}ufvlL^MC7=ML z`5$m?dc=7ZmG~PaUvPNr{lDW?DX@sok1p$co`rSPMvX)~YKPsnId$;=yDnCaSJ;=D&Dk9 zaV$vKeQ>&EGP25T;K}vF+2M#7!x?+qfTXr2I3r>3n8=2rVWH7Gk(KM24#}7Z5;#T5 z0%=!H2bTWikyUmDCMq|kj*V{X00A)_6Pg7GmQOKBKKbRvLDm`B%MXV7^-S5BT|F`%5JNW;7-oNC(gY2(y5&R2z8!PMo<_yZo_L~{> zhw|rtOA{2^=1irI0DX2ZEI4a2UMWRoo^g<)wFE~laeu8UN0%4>><>SB8<~V+7>X`J zZ}58vh$`Q9L+)ja^2tay85>ZvF-Aa73FbION4oa4OZaON!5fdKF9v5;?P_}Dv+vJk z<8ug`_`{$3ZxtvUL(n{rPa2Q>ZoE~!cEN$BakXFbSUCB*#8vR25dMQ+B~u{=J*9Qu zCnHboU&JX&`|nxkRBg`E4O>_5`8omqE=@%)QpB}KD zv+z?wE!?eX*kJ9FvkbHNkqXDGMs4#U zRCYyq_vw!^S8iS>HXB#$B+fjjh1fLCh2bk#+=qL+%ay#t^o|S0ky}D##5dxc1m|P2 zsskT~%#996+hvPiqhO2kC4(%bnaSGTplz}%pzIp^7m_x6Vt4k_zJ8arj^RZVDbm_$ zwhMc&UVy(3`x0u_ZZJpHv}b58xGQ-Q`ZKsP@NomK{FD&maLFUz{IDk^ZKWDjmj?Ex z7Z7HREX?4l<{$$K6XUy&q-e7{u=w~o1^668_gToM+)mGi%GSdPz8Lq@R$ldf&%#L4 zB-G|hW|P|&WD}-^F?-RS@H9Uhi(kJX>$ANnxzW?gGelf7Z>H;kp_XGty*jbp&~&9j zo-@b3mo}GyW+zp+VlVbR#k!I+q1l9oSCtWHmkdye_!iE62fOW<3uaE@CbQmkD5CdT zpEkfT!A6)oKWCRY6(O|b z2u3{CJuI`U?E=QhRj_nV<*KJcd~im+h<936XMV?SBpT8}awJU5_LFEb8rsTTyT+UV zD3|=lq8o0cHFTCPp`@FPgHMRt(Jg(V^gid}Ph?8iGn?PozmU?+zP+{>s5Zh)9>L;= zxF!q&g-)}BODN7X4dFq;9T*ZP?m>~yjfLd*eGQ7uNhy4Mmm9C9l>00Jh~l8 zXj#BcCqhlvXQ_NoU!x^W8tjEC`tLlMX@(qoSU5a$$h#<|NW#N31q4HlFtCxAVtnH7 z+oR6<=%lKJppbN$jLpQx15r7Uo2{&G9?^=Q#2y6ZBNLnBxzEm>3n~Y#QOzSO;&Xjw zJg=(V_241odm+h+goJ2{mX3+0In8%!C@VxkdlLJ%0K8egBi$YY`5r=64rkwWLZ>(Q z@7Pi3$L*z1B1l(UrF7RMcTfWQ!D*P{M(`;3o?m$Sv-bo=&8}9@qd?*0BeVU9{=Gm#7Dz&Jp0rO zSlzy4Y3{`3q@hYON1jr>Oz++(3NU@A6zRZ))6CPKXgpqzMU3V(><~^aEOY$$+`oB6 zJ=497xJn~vT2y@W{7U3nCzcniR~MAMd*pZefzolO$qH3+d>MMvQFiO|t^-W^V)~^5 z;gA?#V%8^>#K$`u>osQm=6GWtfDUaW5v3t35LA__jvl6>wJVTsGUfvMzTH9^+557; zS$`kkRBN6t^Um^C{VA%$NG#%vu~AVpm59OpCsn)J*CEb5?~IIgFeZBkmnc$O-bR_b zknnK@{e+O#oc1<7vBY&IBV9kKn=1KvWtD4RXz;<)VKEl@_1M8;4b3Lwt`27!GxDT7 zl|M|_KSi%!-oc_$5b*!iQpNTcOO^6Z1`5gs4iA;T3yV6Xnn#L?Et!N|lB#PKsk z+|~vRU^s$Uf5A~SF}5%evvt)5FjzobtZYmyAa34=UtORCaR*zwzfF8*3kK{UK!9<> zgT2BpHWfcg{@JJk^owx>8#@atU^nq+`w7tRO&v-#ha8AMV77ehKk|L7TN~IV%+eAe zYspTZ;vmr<+{ycSt%Qv(_OYrOeJb~$Tc&Ay^QYdzEAJl-<1dY zXta1i&$r=A#>x%iW-A!w5NO#fw_Kluf}peIXDrTl>w_)p#dgY%67KDk+w~P+!xO8# zlsN4cP};CH*A3FP2uen(Tk{KPPtmiQ>{Vz>wAEJ%s%Ct(fxGjZh?9ga2-R%IJItH0 z4o1=Pd|RsN-JrLhgSa_^73#18jRL=cKRe_%zGY&u4&jXK?C^Fqd{q*hf^$Uif&e;$5jf3ioSiyLMy#P8Ul@G3YKO5Tvj zI?b?4((L&wC(8?BLouULqK`>!aQ4x;Yp=~`4N0}7tz&0hYrd(NVwTmde~EeQklm}@ z0^uVb&&H@7nX=4tEN;!mrtq};SapZ8GBvZ6yCm2LO8vOeNKbdBH+zQm z)*LYtjMQ`TYDl>vjdawN`q@L{^QZ6J`SxFdpO+2XS{gCv>l~qd`?7b&ym_cBbggaf z&v{&m5Zw9f;L$X97+oWi(9`Yl)+|XOhOO&%X}V)c1WI58{xVl;-t>XibX{k1=k+&)P;xzXpu2MN{USabF!EYTZ&q`E7Lk82!~&U}BNz%()49FCU*sYbjKt27vKV zSQRT{2-lPo9bDs9NzgFhU_Q;p$l4#W?N6B4Y=xN!k>M*AXyOd;*Y|<#U29LQ6-uD7 zPzttuTp)I~E3{4Rq%Mk-M|2%7zgNuVyv#a3yl<%rTn-y zEu?O~;D@Z~H-lB=_W6)-ML8TtRg%uLYgnL_;{=pYWHhPli&t8cya@e#y(6ML+jSf; zu0u}^ET6*CNSuHgYgfy9Mv?du4$3SJdKi^f0*DZ53qFgFI6_LZ6jTut-pyE`5zEfd zc1ZNgxGTO0zUebHgG|3szSUW*FLl?o`5w0>=pbf}F4s>w?+G`O&~uY=g!_gWPGsb9 zuQCig>J~K{`;uP7^0%Syl<3Jk8zR>BpFODx65YF9>N5e+UY1&Uwy+n0Pude1h1Al< zB=rOPFOWDG5aRX7gkFS#UT*lc{lnNQSF7Nt8PWOK3kQm2^?8S*c}O#=uX|!r*G3;^ z)%N%-kq}Jn{Zk0n$A~gSbvrdQ9sj;rSG8XH!~OI440w_dM4bi0Pk_oU+|e7I`|dCkf9p(ePibe zoMX)yt#Ha$x3(3&#}Uk9X#y>3-K+u0A8cu!X61%onh=n)iE$3z4@wl<-heeoGj4aE zxAArg^cJ2gY0pC|fL4`~?NE>jdN4y{};P8@DU9D_x1!DHXqx6Vz!3fz+;FB%QwzMauG3r7L!XoT{(mxmaQkUy5N1Tu{t=!D8A? z%k$08a)i_Ra9Wuv52B-EF)Xiz<# zpNlWP!cjT2%pM?_{6{^OP}m-0v%~oY64&qT!}x0v%{<;AX1K=;epCXGtcaxITZ!r$ zR8#Hh@KaWb@OjTo+R#+x?#T4;2vAN09G8wta<8f?MEBg0)iV^I2SjcO5^Z&@aQcZd z^UY%i?V4Nbzv{%sdWqtTUyeBFo!e0CVb0_pg1 z3qB^fpDjX8L~@uxp{-3H)A9+=sdC^I$C)yV7|2RmKD50|R6s)n-7%j2nPT{BB&7tQ;QGFw%RfoEqVT``) zrhM^MwH1+E{KpjcJ-I?JSdP*Q$$(@p-3Ura zZWwMZR4O3DYnr@E&d<41iKvHCXRoV&VFsFs7Pyp&xq$D?p?R?UKXy>R7=8Y_nc@OA zWq-6#{ns~p+&^#s{`WU~4<-L`v&YWM$?{Ka_H>3E=B2P&3h0k~hh&R#rILI;6}-Jz zmq~ci72hn!<>4z65~PU5Ce!cS&s;rHqXc-56}nCszx2&|Z8Ip6ZOHFTIW#>kxVU@{ zMn~vjj9l9q38UeD*(f~ub>8+-rq{QjOUNj!uXp>W^R|ZF*aSFfA6o-;`=T>>+q^L1 zZSV$EyK6&goR?)gu--~Me>54lSVWxvdRbshhuwmLiBUd0WH^7g9}xr7h_nq zkB`q~K2;u;sZn5O9ZZn9W@<3#{*4rGdOs31O0pbQh(Ur&elhDX>CvZ*_&f`(b<`bB zu3|4^82Q|gpmFnWc0|%1Cg+Z*<+@wm0jHE(3>EA=ibO27pBU78670LC9fl)nGo79> z$f95u75Xq4ChVB1)xj4P?#Efd%UI@9#K|4IG-_Ql_wveBOz3~I;Y!|!!ssUu(S&(b zZTre)`>QLNY;LGU(MhMyWOPyMtd{&XKM16Jz87JW-tur+d3^Hn{> zpFb+cuVmN3neL(`-aIq+(HL$~2s7aF0n5j~rFy{{1AaUPQ(YOSwW|q(YAWIYZCkqK zSGvEYLe#RlCDljmT{Wn693ProIpob-Bz3({zFOUA5+B|bP`btv7dum&8oda&FN#xL zXF_T03-7_?cR=}O@9BVL(g@yc>UZ)wy2;4m2r{eGXF>K>v!X(t>5{?Kh8pqcm5OKO z#;I`K9o^@eF3N+-hMLxfF}`x+iV`FSeyju^ovI3}c%Q1_)m69Cu8euAJPBS&(2QKQ zqi$PLVD*kSLSdn;g;5>szAou~KSJ0Zn#4}WJBps0p-=FYQ7e&s85y<=!Imf$9^QbnKZzYMh<>?vcarYL)mKbG z{Ps~bM(npCF#^M>gQbb0!e&#i;I~1U)UsE-780o{^!?u?)Mx2)FNCtaOI<4*8>g;4 zN^6Igi9@+^ByiB5jouh*Gw7yUrM}?`P{ToWM}AW~b`!oVQ$a}Se`>=;fCif$j4C^y z0TJUqi-BPs+3Y*z5dv4UCMrxkrFARu1sBy01KoYL;sp`tS*CeNfcfW_FKt$)*MT<^W zlsjfwP4L7iL48WO-H)EdUaCf4$#AhE?=%JHzNC3X8LQYyl-Y}vc zBJhS1dP6*5Vy4cl8Rkpe8Me|TkJ)&nmbfrUSh@XG)>}h?lSRR%-k!_?UJl|Y3Wwz^ zr*fEv2zuyOccWSYi+O*G7;|bVm)>OT(BtV!M!cm??-I>YN8*!aZH*1}*(mYRnJi84lg>Ut%ib0y3 zF*@Q2Yi!SCag=U-c+U^)%=ulEj=uYFE1WPx%hLmNacp#rK?^%g^zY3_K2T=zHn&<2 z>2`c!Y_jIrZuL5<&lYs6a8ix7%0o!Am3x(MwPUUmlkPqrKkDmlhD^VT zKHuvMkidFN@4bB~2iH#F0mJ>oH=aNEdYTsftxQsF%jxlk;j{4SNhc03P+q)o|7)bq zS|{%>vDPD+B&8bvYe9AW>O%-qNBzk2DFHFXoqEIIZ5sB|FlJB$Pu9{n1c8p;j^E{`d zJ>e{#ZkUy+C(1h)0v;id)khJpD_(%l*HBuF62s;^S?DsP?L8u-N(ACp-?-%bnz|@l zM|DYS1=iH99187tp+suLIBJVWJa5k=(Vn|YHo=va#S)}o&KnL$^!I^nm~JRxQ+zV8 znvW8APY8n}kfqb3Gy z_-=;#Y!%sO(Iap&$dWWt#`w5ybIht&AJ{}`+Cl2$4&tTWaq1oZLVUIhML6txl-$hS z^6I`Des=XV2dMQpj8b$q@h|bJp`IzfVOw}lY8x`YcIP=R1#_lIJdtQS*Pz^DVdQ_E zkBYQO%%MWNoV5visz^MZY729`l>vZJ0kH1N77OSAAkxUcTr;hJ;HqsVr;eB^0?p{w z{<^^LVY0L(BgWEwzC>Png?aI+D?klAnN(6+Mihr?0!*Y~(#?WvS_j2m^9nVDx!I?W zMp;Qtm1rw!W#7ri-0i?f5Yxc@;ICT28C+}68FN&ax|ducp_k?>OBSVE7&CgK5H!Y_ zZ4vn7N3$!D)(Ugs*iA|`_J$&RUJ|!jC;A|sh$#{MZzj;WpK|VYI4q(44uIKJY>AX~ z;?xCdKB0EPWy2~&%bant3GYz|vSV6I$;6VoXn-kuK^`Sk+{49XaPPPdTk6?0 zV&x{P)U;kN6NzGms=Q!f!VYiL;&eZ6^m8m^efvoR!Z2FxA~*-LFiG^p_4$iKH)g4i z@+3Q&7&niDa#!$Uu#n$7#6%(UrQ4Bac77(Ar^zA?De}VRLqDIDdsQ_C6?#T@P1_j0 zNYn7fDZLw#WE;sChG;+ScE&>8h+$VPyv&a8}8vcdUd zg^geLD^3?Ag^R>p`K`B)Ha@$l_y0UcF8G0w)pXt6|Zn3U@H;P zmg0l_<~A~f?F?1DxZ~INAfDh*N*GSOW|8|?*lk=lVAYVuxIk5Hrae>RU9?55=LQ=? zF)vod(?0_hQ+9mj}oGPxS)aitpD_kz;w(OGTS4PPzZYfOba67f&) zyj&=2=A_N@g%-T@Okw07lGhH)Bx4Z!UOidZgbyz~QtF0Hmd{f_%yxeI)B&w0#z~r2 zOO&Sh1P3WD>Fe4h<9%iILG1uJLen53!=kKtEKY~7RF40W3LP~}a^ylsn?0prRp=^P zYLhnJ>oqJfYNNGQvzJl>h+Y>`?e;RH;*o=$CBtx?(M^rc$!|$?4~^VXU@lAOnG%pk zw#Tz{CJzzJ1B7i7jn|>kYYrX59_JsQ)@$7FgZ(C~HKOl=zjZzRRGibh4 zTXzfXkCRj9mlqA}+eEr@uWWFyX%-y8v(nM4*o8;eBcN6YxIauXmB;&7I@8_YRoV1~ zR>^=(J*n;;av^uGhX)DIQ7E;*x>W{x;)R=d9YUG`-z34^OP=Jr;*tD)vZRUj=h~fc zu{sNg@7IQ7O*bpfD$p$D{Oo6qzvI&>8O+8O5N!4Ph9SPI_3%hrsao~dyUb_I>vogt z-_XO1DVmqXGeTU}lLF$vDi@ZYR)e1%*Tp5JI+H_tTXxJvrlk zeWRZ~>UH@l3;T%Ast$Y(nx{43B6r3NnHqdn<>q{8IWIC|HaS$+eRTSf`j0L0~XVblp$JO z(+oG?T_3n0+4;|M7CS=_{2NYx42S zNAKTHJG-5~Qi)vF`O^0)({fMm!%K@6@7vwDo-^ewr)UzHfjU1Xp6%-K@(8C!OkA9JJGtI|T)bZ0 zJuL4yadPKs_O^+f967@OCSBceW$XRcYOU!8yb*QE;&hm{`GP5=AS zh2I>5`iClsi|wBgE_kW*$-%H z-l7>@A&ATLo^t7t@Yz`}=^oT`xumu@CQ-J#ET>0!)i+XNcoDoj@RLx<=zWn%bJ~_p zr$ft{q?7L-FOX|o@QGGMzdnfGnd7zF`&=}C2}Ee<>+tqR-KAX)-!wXgMz#*t5m#1? zJu@q_^_}fSiQBPa#Uko`1=bzPv%cs_+@jNvD{_7Da#)$)FbYdR zz}CR2*~t#@5{j$GQ2lOe%^XU5g#uLpRp2Nz9?wr+G;*Q%=3Cq99XKJR*C@r+2|vJ` zG+JVhsWRD){Tdb#$ENwOJN+LL8=%jAI`b@U05-6)H3Q-gz3=#o0`a$My}!~r%G#LP zKICz*HFh!r4o1@|IT#pOnSeAD6+p^PhE^7i<^YtFiHnJi?)OT<2kqluiM1So;^Zxi z9YNZtNURT40^^GHK?e5l|6#NO|9|KuZJi$UoWC#hLCN`#LV;EpshWU6+8}0S2`Lb> znu#m;hfY%p(1C&k1VBJlQY;|WUsR(~tiVC-pK5rZ`n5~sDkR)jX98I^dF#`do(QQO+94&qU z{GqxBg8q%_{`s2Nel<|l2L05Hz&U$OEf5WHsJTy3&h3A$<7I~`3c6#&dtLGblSu98yGJeFyMX<<9WdN6O0GA zZ@|B3_akBjh#>4R! zZQgF}#}acEqFySuwjKhOK#|D9R) z!=1He=F6Cuis9T#z+3@K=ZV> z`h{q|!+vpGLCb>f$N8T^4||1GDP907&ADzjO60I?u*Ys#6)i^v0lY)>BTy zaXW1+J5A&TY(ge8du(cAsqDO(9vPL&rpK;Xs5Jg}F{YfXEiU~BuuQz1Inj8HTmAco zvxz0v5~CDyiXwX;UzlSF*hOUw8;avXmz@Ihc2m<%*;=aqx>wXUlcLB|QM(cs;NltL z=SaxA9_HZ_waXlsp~GHhocZ&g=jy}kn%a3VHCI_Uv6JQ;jf>9{eui@8^CYhE`zjM< z{4ZOi-Zu`IUnY@v)s8X(V>2i!mznxxq6um5K80e)jEjKh-&`rU=$ zzh6)?VdgVjqhOCE)^%$e`DSV$`5=1mlO&}L`h;7P#Zl!~DzryuS<#2``QoLgrE6;` zG^8f2^mzwXEZWw10w44;SgO|7#Gqi~UunZIwgzQa4wvQ-wy|x}PAtMP_`PU2^uFFC z3`h9?*{D`3A@s$?eoiGsqKE};M9|IR{B|;NL1fe^pF4MSm_kgF6ZNmX$H+f@M0>%| z4xx{Zys7K_`0pBtlfKp7_x{|)c!EbPhF>Ef`j{w*K;a4@=@A!krwipWm1<-#_|r56 zFAs0R>zDsx>aNg&ALOpJ&E?=*;RQbgXqXGhK_ip4zxHF|Xq&+;Fm&dLwOslpxW(m3 zEqzy6G4%gzSeMw7we^6LS_%{l(!rapX$~taiR;9mud6_PeR{-v7=@8f^VR5^$J4_V zCFDr|P)Ce^f94Qj(hKI0^d`qoz4%ldkBuNqfVJa%ZQFbd2bUDCqK>SFugwn^a8mR6 zAKJDU%>DfA?%&Re(C8&MH$stP6jE(--C(g}x^b=KEe4IOOfYvZ7wyMVN4^xVE|9Od z+WXf}FKo* zhasBni2y(;#l%y1EH`acmn6a({5*_;3%BVGjxfmi4kPo6dtdb*n&|Q$$9n|N4TQYT znudmY>efd$TLb)Zp@#{_F!ufg`W3BNPaTs7i*wl|x+3KE{@P4pKcJ+pZ9tGT2*&=i zw=}C)WKbQiB8IH*EILSmgUQIJ!I%v*jhGaoVlc*G{I8&GwY2y@HX!e>chNOE@4ubZ ztxs&~9#=a$Ijsgci^dR2ywiuWwe7ynQl{DlP#P~Rtw|FJzyn8Alq6qV&3tx>s0}dd z=g*kL@D+9$E2zk5Oj2djE4dB05vA;i?WB#%{7%kg5Gc@~y)tQqa(5C@3o-N3wt^MU zv~mF?yN_NV`I2>Jl@*2VCDAST3E6>Vh3>Ut47%vl*|S_hFEl1N!Q68u;)84eqH*WC;44QF+yt!SNR4D1ID!B(??hW^69eg zQorURDML9;5L`@$s(oYul+t*vY%+wi6N9GBiAUV zModWPrckK#4dxtU*Cg-cSS@{EbYjBE{Re^&aCWj#9N=jl7S?y z;?b4>B#OX}KEoI*diW$J&_e`%zRw-Mc4p$99GH0Fvk3A9#!eUEMHAHzh{pe@v`Al3 zo|LM1GPv<2@9<=ed1-%f*l1&FNk$~WUivc-#Dq>NRmy*TV(C66Rw&HJG1WACCSSOn4n}8MK5NVHaMw1>ERcSo}1gT-754LM$f=Z;-*Jlh4`ib zatwx$H+e{~xH~lxHAb3F6>nvebf9gUW;NOxab8|#m@t%%u_IH0X>)k*F5{#G0(C_Z z2@kS@fu8Q?(n&S1(*_KQm)J<;ADOsJu&SR&7wLrt&Dq=DzZV|qPp z$|r;0xP!^nr7FJ@Yl)j^%C!gGD72c!^Qz}uZIpes`;2?jvo?0Ka$x$xvw$$0r0qjj z(oMfui9h#?rXETE?3Q+hOf`q&z`hR^px(m)Z9_pB z;7;ultztX&z29rvmgYb&p0%&`&_*}N8+A*>Pfx?gOZSDnWNaGPh}*OUVdlm2vU3Y@ z@bIvQ!Qi-18kfyxCrT2TaitjSe`P0#6HyCy?xA2xkvZhncR*IlGyi(KrudDIEZ}D7 zP^X1DbWoO6yfFMrrDz@=5~7LF7;}DxWltPs+ZY@>=gBPQWp&#ZCgOczi7jV`7j})& zNy9{}`-QKfbLrjy$8*P!J05l4%d?j*-pn_?t(1=@AtL?own^c5t_BO&l`Vv{;d=); z)-VEAGdRLRoQ-LI{ZFdjo`%1f$jc!EEc2apoc?NSr#GW>NFHS7TH#BD44!>)lt5Sq zdIpp=v>@<@ZlET_OQKzCn0Q0XP;%1o7@ppsiW0t+WqH^XxhtuKBU)U6kgW>_f8^ba zbV%ZKa*%l*ZwOb?JZrnhek%QM|E+_|A|k~phAi~^*E>St;FaTMBVxvxh1BXH;hIjyqkXn1(~ z&NGX!2b#Yb!`{KkO_5Mq+o|!pEV4;J4&I`5p)K<R&j0QrP{1(DR2~hQHApov62HQ+k(?VMxz*yXM7Bm-kGFlLej{? zB-Y_)1ed|rSU$;7%}Lhbjw=%&(9PG4U?+~(kNphLj&Nk0G2161%6Glz96{gxGJgW9 zn7yH-l-If2XXXjHC`nK?WYlgAP^wzvVwMVq zsNB7!P1=QDt}V?JDd-ZB9E15)f8`r?%Ox=32pla50#U&+glbY$ah8?pRoxVe+|_VX zwg=b%9`|>B@-Wf&N2oVLpH1;M{OJ8?>lswsLX?eStr0J~AruH#TckG~)~4<9Oz=3( zU*diWaoJsCg~;SvS#!SfNlsKaM^gdZM}Q&&B`u`X{U{*#{juzXMYlR`mJdJw_B&Bc zq3|P5&1LA6%qtWUmb4Guz~hvnyu7($WOml3vjh-Gh4?D=!N-FX>B+!Fe%yi8;48do zQO?HAJsGxMS05W27r|K+6N?K_+?WzyUN%{-KfZjfpF4+F8O!+SpS`_S1lT6;xgjZ$ z9DfAnKAK2V3WHSj$~nLwgUqLNanRKFFX{-3u#%B_}y?U)ChV6%H0oktiS;~q)1?HBEv`ucW&&=4DO+$_{ zXAYAkEd{-muC#iv+i6~cTc~0=I(*w9KwhV2cgrB%I>2$vdN?qtDZ8Y-u9`XX()W_5 zd;E%4gA}5nn3S!}d~if9Nv*@haTw$c;CeEnOPr&yp@cabjUS=u;Pr-#u2C9zEu<`4 zoOy2vaJ(T#m3W7{$NQ^$?CQlqORGUwvg&ZE^Lo!9;{KlCte)9{mLoOqN6yg4DoHVcj&n&;2 z5G(w7U)-lcJgdw$kIQqgz$te+#KR3VqNk^e$JZXUuO!TNwr)GnlKcA^U9%Vuu6!gE zLN3KdU)X4H15`g8i(i#ei6+}sX42KkNwDQY-g(y~-v&TRO!rU7Eehh^z?IMo8V%i57(@bBhHBY_u^FvfhoP?)U|qi^ zOa(6eIzhkn-VpY~erb20HbmTE*?ov{-fcmS_Q|Z$%6?}^Vo7}1hO29#{D$Y-JBjBi zw1db8@v|io38T$IMB*TL|MR#CdUCS&@{ zuY>2+V8=C1pQgVWFL>EEYp9f*8*Az9Wrr*P9twRx@nVQQK||86;!VVM6xCtcr7Qi^ zT=t=Z%BPML)7GS#&eiJalkFs&!8NlUFiH&wN zT=}NI$38BJ0|5;%o5PsMoeLRE91g*yg4Wi^UoH+`vz8OdDi`RfnA)@e=;m!hi#jYc|-PGg4y6>_%G29)mwYEFT!QO zbhM=|kHn2?(clKTs?PcUK_yUSi*X{ z14=He%&x*n72^4(cHBN<63@=IDIqUb@#p2mPMUFS@8_y}`Z%qr({UwE5#nZ%W)uU> zWC&{{BI%$4eLxH*mmHreSG@^Y!N4q?unjPY=x&@ecIH8@>jBn!2|;EVRMN)BF@|RX zeYr?5Rc_>dkD=Z47)BnPWJvbn9J}*p0l+-~C7og-4S9w$Q4EceDsIBrXHQ1-9aWr$Fxw=f$_p*?$Z9L#P%640`_r}^8)Iz z?m`;TA~<{>~~qFRrzT|f#I332i!8>*pGrJ z6+fbw<>o+OXyxK!;&Rx%CP|w5ov~KxZ&qGr)!(DUURw~mw;KTmAxW~f`1r*E-#w5N zkiW9Hs@nH<{mlJ1#1I}hdtMb`Wy* zkJi&Dq?La=qbh1YmG*|IdAa}&_j!Tvwit?yYO1Q4e&xq&XH~J;x8RbdVpYR$s_*_v zo%L;iUDr3%N2iR*X2cmIZM7#%Z&A6OzCv0mpCvM>tt#K>Mhc`>AAD7Y5Irav+N(`Fn_-1nuz<LsGp5G9z2i7M71>NC99?n|I*hXjX~^#FWizvj zpOSF3^F}cqNhKoEJ@s_h?sNV!5;$5bynZ+XxG1@ci;X2bQ|mXl+Lp#&l{WA(L)#8@ z>bOL+N*olPd zvTfe4=7|tuN$+?|IIg)xY&>7>?OmA!%}?A%!E1MlKa+}%lKKls+c`Q0R3UTvKlB=z z2E+TIZ@R9OF8v`yX_4R;cq7P)P{{4?cWoMgPVpHhxu+lI zCZ!LZs`xit>gGZBa0=!@uoND`9XgsNGifM39|iCM zliEl|$eg;)tGPXMGj&8e@OpW@^*;Zqc+^DWa=>T5{bPIb187m9%kAd}GVp*urwG*g zH*Ng%3lZIPL(Xz52GdG(xm<|h;3Ae?6FJ$rJX?g^uAlJ+hcT6!AD6zg|= zGPP^aT%mKw)@NT08!_^~%R23pf2x(Akqt)znX;@{JukfH(}WIw-&a5>>Q+4PcKqm# z{2CfDcPVAYS}03q_Az|hyBj$BJa&KfGN>GBDca{Sjwk|$ zyybWlX0~(nDMzR>*z{Ms_}Pez%VXMAQZ}{cZNj{Lb#xBw9UUQ|s#OJU$pgas`tZLl zm4qRmWDDMYd4IRr;S%i#d@|0RJA-pMDLhIjQL&5*vYrUN1rt zy53F@echR1O?r5_uIY%R#pt1Hg(Aw&mrk!X1O~sLi?od1fXM=$HrH8#o)*)iut^{~ z2HWY5gbwxUlS3bpSfk=5(@a%76yD(0izJWrcKaUZgVaaM!st?GN5Ml7;Bsom2vg$x zQkT8XrCCYviqB};ZjNa50l|?~@p^Xg`SA9eK<4Kh#l8J~(YKvz5$=xmcJKQ$OY87* z_<|J6Pn&QYQ>B3JkWQBg+Xsphv~+@FWN=Ch%>Xn6LmtJ=ng5Jbh_ggyjJ(>FZ$bE_ zUKtd0N;}*rh-@9Pzg1_!#UaEIK9Ma+b(VENhm6j3GZz3s=_@h$?xT}i!?yEyw7~5t z&^RwjVVuk4Nq-Dk!NT*ovFT&;n*V(bzUanK?Ed?efa`;9)m#!ZqSR9-YrsyRI(d?8 zohn72TrV!5#PDlWb#EU&2XIzco89hebW)?-eN5q8*r;DR`El`Bu7NA>k_T&y?U|}? zjIIBx16QUB9j8#0K_~mo<9cV{F{m>>KF(=4sSDN*H_ZV*5D<*S#DJLnbC)FoF!6#Jh(B@R(cNM|ynjzD~?<6HZ% z8Q4wl9NR%zDvl`$u3%@>^u&!4-rk~ZRjI$L3oALy+Es#F$YxRrkO0l=a!t6{dkdIc ztYuVnV`h=2zAgGN3B`5*;<6Ey-L~ z)?CyOBvRol+y@m91&g)+=a$NH5HV2Kmsc@|X)lw;(z<9lD$b8xa?QNmypYf`D71y} z>Xll#SPB`>BZV+e>#O_)4fTe0tsuez+4~J+4hG^94)xIs;G7&jpZ?Cc#!xI(zDioO zZ=M{B`LC(Q!^?!|8)DTa8Ey?!V+f-b39R)GaW2m;z}KwwlgcRi3MwT9DXPJrhjqGZ zfDUyfm#8=$<_hvFpX-DE-`ruhw@o+9b|?!(yBN~E?EI&sR_%d)R07!iu@7UTS_E6$ zc)S=A{^1eDQf>$gY1VWG;|5G&V3zdCGQgQbM*bEbw}3>ZmR&WQ@XWc}C22_CsYRe| z_f`3}5etGqhL^J_p*(IIk zv-$?qGI$425g9x#e?(bkykc%y9=zf%)F9G1MOB8HhnHemWBDT@A`pbQvQ>T+O_5>V zab|vvyTg^@iB$LvkiOX4r?CGvO&~Rn_|c4&UM#GaCOT8DsEJr=FP!Jp4nvMSC;2`K z#xY^n#+OTS5K#G|eL!+vV0;{J8Cl~`|J|=PLf*>FEo4LmjjNa3_0Nl@3l}f@kL-u}PB7J1` zEjN-5lFA5gMsHx4$$Z@%aQujbE+b)RSAV3{HMiF02d8Ip+>iRbb<+R9EN3Hq0cZkp zCV@v5>wQ3&0}5uuD4cEWdT(3ZrQ`b5iPRL|!<{H|h}?(D8$Q}}%MFLi z{2NTLF%PY+bU`q=O*6o(me)_8^YSj}_^nQ9?ZXv6?f98>Yb@n7K~i2O0|gmJRL_HA z5I+RUpcw?3cHKLo?z@^j4oP^^M;#h8%tB1D!Ko+z3bB-$RI$kqA=0ouYvt90;nb%7#x(IIBzX{8n&SpqYcrLFls+HYbX&5TJ3E0qqlIQR!yuP z68Q(6!lhTn%23|lcLw#=G)hr-EQl6_CgZk}5~hp1o+#jT(QyR*zJ1gl?cS%$_wS}* z{4+q9Z~Sid&3FnEo9ykhUSH9bKJ=lLiW6t6xDM#YIzlapKMozb;i65r+y{qf?&>G$ z?|M$uwVkTaAgUZX@TKt^akU-V-Tmjj`4yc!*Mr=t6({@7EESR0tChDpcX4|Mj+eM7 zh^1s}f+mxpgcoguG9Wdr%!7yPu-ZCpUgrgHaqfJ#@Nc?WnSo=Ai00$s+QJpQ3lf6B zVcGb-6CnyI&oj}M{0^`O;2cB6-oO>iOJS8rl)UTFgj2Pv)y zWkbp4096pD*}1oO3x$X)P6(>y6vZQ^TR{5m`)ffyFrR`dI;E|7-S=E3zx|3|M8E1! zl^Eh()sJ1mW8{#C=ep8_}B0NnIcOx5*h;BySrh8wJKXlGtEB!gP zZl&U+jWljqMPM(GhcKt(-T9uuyRynYZ|It ztBpwd=vXf8>iA+Nj^`sDQcsN;Dx&Rx+VHG-h( z4R46+wohBCEhYD`b3gAGze77fSEA?Upt&Ev2!4}53(ZCqS$m|aRsB1w75Y#1j4xB@ z4<6Q;$wCgY3fK(CMM#tzl3xhN&aUD?S3)Z#QC3VXtU|Mr-#rd#4wo5dLFXyOIXgL9 zkhl7W287FNSU7~^hHd2yZ`lpIKwDo(dQQ)&abFPoQNn~sOJ`GgezFG=ElCdB8awbe zKh=liPpev<_gYJz?r@!qyC41pMnurz`++09FNi_Cq6%qBunj`F&w8qBo^2xEMZuf* zpWVGK@V0h(neFI9c_|n5^>lZ|>84%hjMooa-Jk*n zW|h8_`b1Zji{zso;hbuG@1x#q=dFgmp(}R=nMUH*{B5<(`1RM0FBEx97n0VbD+%@Z zrxwD{uOWC^$8qHnNzY&5r-=KLJx_8mB!pZxNY5}SWCpO=C1vedaf7itAZcCtBs zEXpZ*A2ysy4;6yT5d;o>EX8#F@zY)v;Zn1kDjb{j5~JBFFJK(4b^EcCO%AS}$*?LM z;B!gMN1C-#3A-)E8QUCAEXasf#jfOSnd2u4a?Fd{zyb@es%h2)ATRb$xYG(8hR!KU_PjK*W!+7B(J#Fk}XM?Xr0VT_|ESsM^!Brivy@Um8L=YDcPl`lO>6@ z%aP3!(SIvii0ff((OK#+2{1VqS)b(n1ppovr4c^NEuqAB*1UwB|26GNf7Qr+Z{>r$ z;po-3f45UzaRx=;q*7PKEK;wQqmPqx4#2ONg8I*fhmt*CHZHclLJk)JZ*MHGlLzyR zUH*NSjx~l)ffb)4{#+z5knYWKyJ!eR85)(4`JlJLLwKyv{$Dfw^%;+3zLuvN$V-;Ttp5-4eR+EW8-xn}DTz?#J5!!l4g05T zLG+$0f^(ecix*8zGjIKJBrL@?Gr^kj{BKjdlr^HiVN*f~i06c~+p8*1WBSI9CG#Dt z(6R>EqZU0k?8+BzfT;QU(rp1Wyba4A3^n~E#|fQjoX;cCAf0?KK-5IuE2e><(j&ma zN9bg!~-Ss*EjqFz58uw*^ z5x(4o7cEoYp<`_z-8#^oMxt9b_<7%shg3f@ zn^df~1@dG$Iuo?mYigv-*r_ikH@ELEZwZz0d1wr$7D@=*=6<0$Nk)=>OP6=_v-d$D z#Qz9NV0Kym(+})?R==}HR+5Q*XH^t> z+Y^Ex$FQ?BMtSf7T=vY?bJ1qX8W`A{b2dM z@!aPt;Bxgq-EF~_-Y-+&DkWooOdS8MAiUqJK6M=qVs8z3QvVV4s~AZ-{8)7+QQy}D zr}MU*9(@w{W#`&NS96J+BXxdon@CXWcwgF?s%YN3R#kyV?rL9H~_qe(ot^|6u z+tj;{o!Uw*69EIBuY&v=Pk(zMt9-Yn&Urd2$@Bap^>bhOCY<=CH%PSMGF*ia*$KTA zw0PS@qr$4|M$3fRDAR8_Q$y{oZus`Iac=TeG0%N|(|9cq!7+A@Z%kPvpgVXj*%0<=Q<ldV7CYH7i%cQ$8rsT5*VaEtsIkV5Z`viU6960~ErVxA zq)vWxH6a`M%%3SLt|qGg!b7ofJ52-KjZPcNu;Oqx*JXX82fkhHi-IihiB|a@-SR&} zluRrBU%pQWwhvqxrU)iECK^s9h!bdqGgM_RJQ%$|N0g4jaG?rv-)WC1vx^aoH~Em_ zZf(j36uyV=KWq_zR=y;?bK%+6M@=7mtU}CAi!Pa?+p3 z=sn#l#v6M$^Ei7IjUB&gjOwJ!Gkp}JcL!4!cG3l8Ek-$?F8G@8hxLYg(=>^Qu`syO}b z54M!Jv?kY^OYpp~nyPY^FW+ft@ly6uQuefV@w{B3@Y&@lnwvwi@|_m^j4bGqKfxj)S<}AixS0h? zTbi0m%PIBNLJ6(!2x-}wVJ_zFq(rwV2g}0s3PF~H6*UAIT;T4kk3x^H7(t&R|1n{i z{jcuiI?jk$a|L&DZX=(iTe~vn5Sh)ko4_r?cN0BRv=0)GV&n<51;@~+H3K)MBobEF z4K^fiy3B$64it|!J$q+wh?z*@s_m7>81}>SoC>u>F3>*(RM8}N1q`1D&9wroWF0Gm z@Pz3jj3yWQccO6xYCpZicO%eYs=>E={kJK3%V(25|5{8YGemZ-7QJ{wG)&zbX@tG1 zppqU9(2EXe%kJ?m^2q7vnF=l}5%Q8&XfvwmVrw$!(-;Le`%r zjmtATdlp3Qd|x7d5RBRI!TWhV+Dp2;1Dp`%F8!%pEUAiv;NO=Kwn3Wo9XUv<;4gLh z3~#P{Mgzl!S2lE@73NLG=LMmdNFO|kYo7H#&hldwr+5{ZD4$~;Ne-6(+gv;%lE*Si zIU#6MfmaFMie{pBwtl89X};;$1eSRi2ZU-){*}HZJ7vapwGOy-aj@W(`tIrIgb1Vq zf+(H+%aH6t4>0jKn}HOPrFer<0WFKNDAyFReD+J$Vb57^Gcxl2HDkP~GoMR z{_ne6Mxs|G8w$-hOSH9`rVNU^FLlO`#_HdR&2*{4fRnG6vEsIKq(q2 zhEcj|(qwONctq+X*uV)gKHGslsiSch=c_96%v`Mq9>4S@D+(7<=MW5tFAPx6Ht-yd zw>=XE3|wxQPp=6c!E>7TXHi7PE*FSa!pfZi)DDm8<;R2S$Bvvn1)v#Y#9xt7G^CB< z@Qe~82zrg}EvE9sC{^JxHybv#gUX1+r(`@+=28@U;pfd~+3<88wjJ?f5?j+Y;he@# zOzCdeYlnrl#tge1di{kQlMmvGccx69ex$vDdErOvVzaCWky6kYa>be&B6G(!-I#mD z!fMWMYEHM0&Zckw8^fZmec-Q9x89HHNvp(D(lKWN}Ov7Le6Jgu-*N7^N@ z%qdrMV&fQ6G8a;lv5<6=^N^I|2DMjrv>@4YFD)h<1k&2&AiVZum-Zqt$aw~FG#Fdk zHVmF@Ks7AbeOxH|VUvOJ1M(ihc0~4X{LCe0J(FhVu+u`^4V?oT?#Q<()DB920Vxdq z{j!VQRVnvO{z>vpjgR&X(YgF-Ki!xm_b@-&94F94r?i+wUJPy2m*&&4ui|sfe~0=z zW`-B+HpU=!J6Z=5X61IK6F$zKkb~@LPWV>muv4ZJMYskxnl(-Y75uW77(1!YQ3J0I z22<;8A*UL#J_UPNtC7^ubQDyj3$qs;vFMp8s0qu|%pcqe<9>u~YJxVqcVs5h=5)Kt z7_M84qf6D~Q%^SP#T=Oxt<}BnIcH=<5fsKo*PhR4Y@DgCp6L_{dUE%7!M}eOGAp;w znO)ae8V1)sTf}Q1^Y!*${=^>#ZajlLXLxIhzk~{Z@ATaoKdQ$#&wfxd;0st1Wi0h_ zR|v%%anh?HXY~7UdO9!hmJ^=`2s9Z@Y9#H5<9Gp&a@DCOKMfxL%ppE!Qn(R5 zbw_i;pefCK{C4+ks|b`!h}j|a{y`Yb`(6Gx4PzCUez5l1;C>Fzrc7qalaBgYd-V&Q zg>`o@6ING|+4z!;n-M0&VK#|<)}Tqp?Owo9!`0!l7LA{!kFwuqUrl=>2Ug?4Hoq8# zZ@L0vF)Sr`9&sXypGX}BH{ry13W%DdXiv(xrfzO0z$yM2hNgc z^dz^%64UF;Kc>Kggh$MTU-23Q>ca^iP1inzSKN9BGG0jG#-Yz-MLWgcTG=)BAbKK$ z4zP-Npk|{l+8v9feZFB@Mc((oDvE0kd%eH1R>F=12Rnc!|N3^jW`8b$$769@_rOrw z-uvZPk}XT|gVfl;=PQK~N=mbywPk~K*ezwlYeMI$s?;lbCnIk$6>YMY*}_BDV6bQw ziT}!|Nq01KjP?f*w*MeuPSv}0b|8+_2S8H1K}es$<;VT&$?Z?*EIKyL7)&Lrx9;u8 z!i?JqXK?vk@GEdgI36uY{OIZYCK^=kd!u;XmNyjMu^adrmAwy^!?3_bJtV8YALaFiQJPEM5YOe zqF@*9-F}f?seg3`D)=(%(}O#qq^ChVBHjjrH}|^cClMZ`^cLl=tdYt3ny?rmVbs%@ALYCKwd0uV6)|$tM%Lmn><~Xkr`uAYZ1Iejmm&d2-UzW#z z^c-mx9xma}9rSEX&;2)4$Q)iwtN1T}^{4OIjlMlC$*oh;-oLj=8fh!(X4omRhL2VJ zEzarwbF-it(K}}x*t80D^1OV0QtkU$^TwHNR(N2a6;G;&z-cV9Gw|}J`gPs@gUsda zTvdc}E}A?f+z~ZIS@2%#I&og7d%bp41vhtIC+N-3m?_Ye%?yh^*=1m>*ZABW;5X;A zTJLb&767i1DQ!k^N!4r0S=PrpKXw^kvPCbm+VAL%H>x6mn$=M-z4@Zwe#DHYKhOL= zpM8ENO!h^nY&LQ+;IcUN7k z*6FGz_>`LQA_HIN$2z>gwMtLLY}y_~+iNY%mU|FLBMMb&J>5?Fh%o zlAuojfbCF1v;&cr9NEp$My;I9^#_i$~O=9rsQ zj_V(DlZud`+oW)$7eNAOn3NTEQdBo{hp6FCt`bbnkj}0X2+yUDkuVY-DW2Oz0uF0r z+$nKnO1~fhv$qL7x>H@izqU~$!{^lizrM(;x*puUQZAyH*!Owkk<29;+q{Nmu2OOW z!yZbRb-Kuqm0n45RB<_0iK&kzQ5dwkz;K_s&%7ICkWW6g z`dvb91*XpZFmqTUxflgekxpk-j06*gVt*eqd={<+LuC5pNj&KauQ3m)AV~HdHdI4m z-b#ja&FPMkvg}Sn*2elL&#q)?s@xQM99sic z3Oz-hagvks^FzX~ak2MvDtjb?!7(Jc()0MpxzT0U&b_7}yvqSQ)SnFv2$U-77) zE~NRaNl2}x%<^p#?nu8fM%W!EQt5Fy8=4_C8&)uF;DiJ2zMAN))cj0>_GeAgsdChm zl5WzTrqD=uaGADe3(jBnvAqTHzk0Av{w<}QHKkV0p;U=mGpHZ^#6RIF)#^YC3%`cE z62BD_(HQZHsGb~{JCsw*d!9ZI2(*6tiMQn#GgQ}6Sg1oL&rjQxpjDM94}-0Ks@dMA z&V+ycfYd{zjTA;aN6t6h@s_vgk|a^tkB&EGquJx~)85rI7Ir*Ar$Q^5Hk4x%#>?7( zJf+e@J}$Bs4iJnAzgXlSh2?c%@{oxuN8{h&?C&knzG!}C3X@vLr%kT6ZbRE*?R-7Tv+`act9!l z9wF~;`@wmOVNmry#$NlJ5B_lhDHmDIt19~+pOB;qbZy_`8yYn{O1S$7Z{n#}-uCwT z#_cKlVem)HW8wJ=mKK`oBp|^;_iN96zp`jlV@OOtiO#qNhO%=EdHYSBA}g1&Jg_%r0in#q&Zp(e0-t|->QoDA>j?j*d%Ar1s`houL1n^Mu_*g! zO|MFiXB>~QDUu@Euu?rn+J}RrBTBktBY~i8{~)xCy^t7R@IegySE!)K3VOBWde8gzuMm%HUM^Mv`fUauET3Y!kZY zjvWqmz*uJPZHkGnCOY@Gv>=~Q%=yHiq#5jW1!UrbL88;4qv|LkQ@p505CW2Niaf*7 zCzbYZ*6;@&n*pauM2{cfNPqg?o;0v2+fhqr6cDv8odWy{kkMG!nvBI5NjNJz+h;V^ zMYs{*x#NN4*t$($=Jc5j96K@a1$RuF2mg+|H7ftgC8f3^#tJr>K^ZRRA;jig0Z&pV z3lNw!*Os@%-Vzvr0U^hcnGv;{TC^CBS}GZv;Q9rwiCYQUozNs6|D=Qi-)7MK%NxbbQ|m4w@0G4A)|m8`yq#1 zJ3{#*a(`L0YF`P|>cYi7v_nzK9C^%^-a#R@{5c!h{Yz&XL<)?-8iGI%F^jD*X94|8 z2S4tXiu%% zSPlq!Kfb=+XVrZS16`-ywVQ#R>v~VCfQJ^hbHM9;QIVjI#kp+?+yaQc;?$XTUw!F) zyvCo%7kzhlPF5a|VlV<0>LRIwE*S0T{r=Rz9KW#z?T){)-~P??4HNDDT7Uc;RX*}D zR;tE3f27;+dtNn4i3Ag62M$}dYeLm^HQ8zE=-syz_qvhsg&>aiR=WL(pzG_J6%8W3 zhXP&UPaFpsi+(lZ`R^;!`+o#h?X_dKwswO}t(aK1O6I$_%N21aG0k^~*p%G`s-!Ea z-j|K?vYdCFm&n=SKYgvIsG#3Ec|;Os5dgHm{;BcCCO%G{7<=yZF z)@hES#q4vWhG_j*s?Nlv<4imuj#ui*Nc(rAMTR*M^== zBT*dO)Yk>EXe)b$HA5Ml)l9Vf$0<0}cv)Cr`D)PhVGd&Z@x7%sUk$JOIdgn>pq08y zT~W{E+EL3Y8d>|r)63%o3;UuoW21?BG*H*ijnR;&oj2(1`{T7zGauYzt`boD1R>)` z3iPtE_fyUFe&^jAEm+}9$;S%qhiODoPLM5gAnymQGd_|p^JBo{t&erdZg#iT$LZQ6 z3b{y$(1uN-o6FbD#3G`ABNv&IMmU+Xpmzt=*{#z3AomSRG&fn8GZ(#}v=Q6&jy%Nn zkcQ!rnHq0c^a4}nPps7Zq7mWG(oO2GUn85VhN9CUd#rosb~XLlPpu9qvj4}`Mon`@ z@JyZco8P()-JfZg29H`};stl@Vl+vv=(<2(y&yB^fbbC*U|~XDyeX0*IGf<3Ez}LF zCJ+C#o{wDte{nN=8<535krYfeL}IyvItTx~%GOmmfxSVXGQ>9vtIf{we#R_Z0NeI@ z6<&|?{{VkLfWI|LK8la|R`5m}n#*qwwMEwFyERmW_tSF`OiIM5JxCv7Mi|vC2j>6F z9_W_^W;P2=Z5Nrj@3I4rZ9CIdnF3;3Du(Gp+Oa87Gh@^TYS3kPRfgOSsKEm@C8?&) z40()%ydk#0^ae$PVf4~kAa;O?1Si?%&+o9i2lZ20j2K8|Jb0o!YDl+jNJVF?{LTsb z$+$BZ4BsbtDIWq*PN_q-oll;N>zBI|2t3R}B$N&fNUH}2s6x;qo>_BnXU%LQeIBMZ zi;xq3C1hsAJ31Evf&4~c6laA8g(b8pe4c5AQT z?3_YsePuyWl$+BxuiGSZ+b1|<8}YR*gF?M%v@PxC+ShlxCnjdL53zdN zYNM~MU$)~~c1CUotFyemF3u|=A*ZSeG#|7-GdwmitaMb5YW1ufmv0M@P)5&_r`uE>ozuxlV&6H8TVZ@natUWb! z`^&FB|N3Sdw}e`TV7!G>o5A_3{qqoWiIZ*;$EKil;?Kj+K{Y1a5Py zQ{Lso0_I%(6~DNm$U5g3tOGsKRR*9O2YC}aqe%OTz-4uJp^@`iN&?ynPtsBka(l9yGEX-oSyiVwjdUap zw8yhr6S-XpgY}f|hOj66 z2_i{Tl_*1~Gs522lF1%WJ%Y^k^fbD0Fr&OciOE|~Lhld}WB51|!<+wUe-jO#(i+%w zm(8^0j>lu_6E)~W@@3DIJ~Ajjg!Jc-5rcSZ+DdAyao?Fy>@dkED`pT=sG$Pbzi{Iu7(VEzxM*LskwBaZ;=O+jd`4p8N=fGMCUh2QFu*^_W%zlx2QZi6@{@9glTJQMmW0HZ`VMKks<)q{B^FGsXJMh)f%l0$H4r{QiA@m^uc;P8TgJ@ajc-f<0^BusG@MT|F`-c19ZmVuP=p4sT_P z^|;GGJ~5DY{kHodQtSw;F5UL^yO!L|l@dP~QVuQTVo-2s+|cZYFKqS-4~Y%V7@j^9 zNWm_vOS^LTddHwhb8p0+v@JR#+||LafhE#w!0(NM&l~1;)itnEzCCa`E4zs&0_R7g zkPSAcId1hTY->SS#u+nXI?l51oZTCKK zrfjV2cdh@-bjKx1MVnMK+M46}$!gbP215jnU~+((0i}=F16NSHvp1Z0DO? zP;l=kripe@-ic#*(piK4jLaf-L@au`ubcY~&zMpD-ASIn9xE>d=QX4Bds1^KzSU$Szd#3vS)j^$mj z&(rh^jrFL;v`nuZXVMFUTnqA2Zq-lQg`lQ1J9OLmidn=?m2+qwEqQiZ{Gr57Rfb1M zz%7@Uj!_9=!^y^)qmSOYauSFg6zwTZ2)=ag6u2hVd$#Ue{r8Q&ZAD2}LIXWP`H?&2 zkKMX*V!GH-^yO2dPwu)})xuWdLvEC!k(y}A@ege?B=s5Y7Y=RtaO3tphTF+v3@AjA%;Q3wi;S?@Jpy=52P z-)ybzcW?ZB<=5wZ^P9l4L&+D>1AP2EFJJKriYqSflM$EOAa;hh&CEob-EMis9YQ># zfkvjBVAJWM(Y&)BK7pPYJ$PCYU|vZ^tNqUhhDRl_9?*+^AsGCLgqYCjG@=nkVMBqR zM%vR=8>vT7rGir&v9qn%UBE8as>e$c&bF0YWRyBLXP+KyjjhQ$SDoz0rpLCIUZj^^ z9B2$?)ZFT>yWL6iAMcInEVpaq)%xaBeHiULn2lThZP4bxd+Xw{h?aZIx(osaE zRq;@r07ZnjQTpJv5~#&Wr}ew>gad@L#&&@zt)Rl-EHG-8>7EV*#Y!Sm$$&Rtkb_Om zui1f+?q`Fkj~UXVA>c7AZl^Y821Z9-_6>6Nh`H*Q7nab|(1s{v${%=9Md4K5L) zE-f}W$nB_VD2vI+4vH;_&tTNG$|pqnDJ_N@`-I1coVu0x7u8}KzDzBaqsCA?gK{!l z7}8g(R}1CL`nJ;8j1=$iRG*0QxUBvbhEAy^Js4ymL-GT?!9ip6z32^-#2TdsUKCF# zPDoF!L6=Qw4eXBeGaMjjw1CBJF09NAj>`&(t<5jx^^f2}4)=s%?WHvbAHP@Zkc}}U z+e0-dyQ8lxF)Qw7V6dxaxVv|vS4e$cxr8eqwdD6?ApbFtXHm-?Y#!>W(&I<_aKnf& zgs0KYS%1rzW%>s@cSwn}_Ha1(!I@O3|F$cP5IZ41TI{Sp6r}@(4LUs{`Jqmf(MkER z-#;V|zwM)#o(KZ@r2?K{Qo8@)Gl!0R;}lAA-O6I;G5^bX9GRg&Puf+lh=9ySHe&@03 z@w`&pHzjuZ@?ByheLSaIr#%^Ei8o2HGm>Nz5Stv?erM%)!Ns`9+aA-)8_?$|g_+5IgSv z36c3-co56PK;Red$qmzk7TIz8ed9A@JMTG)NycQU^Zx6NwF3_rvrdYgV=gv!=M3E~ z^u~?LC!M@K<0^aZO;7;DPV;roxYC%qX{LE^6z;g_H%N88=;wdlKYg_H@{Yhk=9sZ{vJq{A%MQ1K!UXS(^mx~-X}*rp zlG1elZP%J+5j!n6y+X4>O2_209Nj~Soq~|#;n5KR&388Am3yRyUv?`KBEu1clPy&U2GXLUBd>;smsfaN*%r03C9Fa>QtTjV*-*IU z-rZY2-7>wl3qC3J9e89TII(@PC%)dX<-*mlCb{8q8^liE0JYh7^Py~U_skGAcHBOn z_^kM8ZdY#3*{G->pT=3v?=J|W8A60D>9iWLR6L+naup&*U8*y0yai85pcOUcpKq-U z8fVuEMyi17>8ZY26SKFw`XYlC*j(s5+!EbaeWfw`WK-Um9;#=5OLSAw#hP^2(az}V z^y6J+UcBL=7OH0#?N&#%M_2jH4yw0Q(237OiK>P=vj^MKnkv0>BafMpff<=f#Ztycj-%X;oql5J$M!S%5E>x7i?Cbh>i`SnL{^ zx-2r)?u~!r2@n=8w_UiLjClipSU+ic(R_a|tsOxylysyR>NZA{;mH$_!Pf5EhcDPX z_n!4)Q#|tkVf=%?ZD0R5-7iu$F2a+=0d+to?8d!5m|P&Gm)80px?ubA2S-+{Oe3p;~z22yhy8#8v`H zYf*LT&A|Hnazums1;A>ZT-)E!nd}*Ka`iT=r5}Cwk5$)p9;{9)P~uXHI1tn*I|zXW z+Nt16<8K7mzWAQ)qGdLCxSFZ$qUE-KeJARY*Ccx!Y|&CuZF5f;o1QpKGURQD&b++a z{=hRUZ1E)3X4b@XJO>w=+ATJ-diBHnkOY-Tj*6Kt!2}A{rx@G zH!>MUJD-_&EjrB0J0q{eZO5fjL67i$)%^gmGtiw^6K1zJe7JjDgF8obYGHSA@ugLV z)5qKMZU;t%pUW909y035!c(q+l<8t;sN#BTxaXyuafzDv@zlsgwD810CsOQ;=brEj zqxe?{WE#R2(MWreE>gn%13T_Le5qcNbmd1H}%nD)IL1V@Dz? z`}hI{N{;YGSY=%aj_(=7PFF=zq}zee7KvI74p^%gF7}GM<=~h#!Hn4E99EdtDp6?h z(Jy+Hc#^MF;sd-zqrLS$4t{i=h!Cxl18voTr$W1AO7J5hYzWB=b2*v9nN$!X#I;J$ zexXo9ijkbagq-vmJXN!tHxj$uC9EK|`A+?<0S9~&a?<+mISS>dppCM3(<%QnTKl*h zFOS!zl1*~>5~<|Qn0Fv{w(T-|dFfPdvvhEYt6|RS4+7$Xn|k$nm{(-ogj|EaX;n+c z^W43{qOT^G^<`gNvNyE2bwmL=5TcRw(`YWM&Swo(_3*_aA(27@bPlz=m*N#1ao#s; zq&dudUuaniTdKy(t4HbmVSBy}Z!E9ROO6S1bjux)sPUx0pgYw?r-fbnE`>YGm>q78 z=Q;QDmlG;jLPPh{Yg97+QcPzHK=bhKT~h4&(c$#5l316p zuz;IIJSj1<92lVeSuWu|PHuTDB-d(}e|bJbh>w;VYwC;Hx#4z0TJVYRF#nr{)5~9s z@VPKu>_|J(%EOLWMX?73IwDQA(h_MucwI} z{X|nPCBWrd=iA5Js2EqKADU8u?F>w~^9X z?#t@R=qht=%X9Cjy55v^WT+*C)soPh@7h@GG|-yB?T)U^JJ()ugUyU-DZkiV<=v9& z*jjwPwe(VJkq4rl0EU)YCDKTHE0d0kCff8mgB&`=86LsqX+?48 zZrHuH$_n4s)aEbm7R97vni+Q!Y5O56Z6PJe=DByRi44;Ae|!J*+U;)U-yUE2rR`!| z1z0aKyRc~=zn?>7DZ_0=1z(ckLpky9EeG%>t+c`CsaZ}htG(mdAssac#_1S1wQ;vK zo|6I3vEcC9$BrLvI<)i?Jc{pe{3KXEyRs}H8$5Hhsf0O}xMF&ktZuXUi9&WdCu_){zlTdu0Q!MetDT$_ zw71i%tE+OdGh42D=NR;$$rnJW8%s)6ji zps+@*`rbvYR*~hW)rld%s8A`?YNeVepniyY?pL{S0aYn=It`5OgZ#R|R3fa{@n~>S z)Xew19WmGA{I10h8~guQUUc-iHQSFI{xZ0gFE_l?Cf=P9L%OGKlOG^auT~oTiH%=~#a3?ejan^C;{? zWZ*F~(vW^-`&Zl68piC5BzZWUvO7(ovJgb4Lxw6tk6*fWJLw+B4u;7muzUyGpt7Xa zDaQfvQ`s687ZY?aWpt__YKVt0J`s2oIpHoA+g)>e5Rn2wxUEfvHOWylDc!?zqy4S; zW+op&B;1MWq{E-tM>R8trjOYn-tM3?pNhTNVYj~Cx^6>gN{gH)1rN?~QAc=**H=c* z&UkB*pV#(%eg%C(sR|iw%DTSGYR`sKMN&C4z~Q?iPPYqMxCo*UBR#n>;T@fwk_U?& zJ-;fS;<9Nn~zV7NBUX@iyReuLQYi_7g~ zgZqKiMY(3XX!VUsYIR)jwX^#i{7a|GJ8!hI-8W;*j;OgQ(|z@)hvRB^Trq<3n9XtL zt=7G0l_u_KOTOiN+U8_<)ew0*R23vORaD%Sprkq?a1ZhW!E z);FW8Uw|MY_C#Z`w^#3YQ}~tBCl5Kr)eM7Qay2qg?tjSTW>7}sERUC85(c}Pm>5*a z>!FvbT``D3jewb9Jl(B?w7>J*3Ws_UZz#d@8TmT`$uaigj1 z(qLyy5A9l8q4O|3yfV(Fqv~c|>QPQ>XkEs!?keAw{BvDpuFbg@S__>(H*U^#Y%jUg zQW>I0m3kCmcNejHQUzRkYpFkrS*a)Dx{Qw~bXMy{BUia>+GD-gluS4Pma3H8=|fA5 zJO%%fb~MNfKzVlj(%Jdp?Z}-R-Te;^WuAW zE`>!ayzS5E)p&;jf}|)Z%VzO1usgd2gmA!T|5?^jRx?eb;Vm2O>Ob=CdeG*>3szV^ z{Z2_tCZ9Eq$TgFLW7+=E2c9Oh)gn{7XWwB^n+;wj1WWV?v{|&=4xi!^PqhtBk;gvA zs`%W4uYF>@c)8sIQ$i&HwXyFwNaP7W8H5bGFb}*+TrKlzwqH!a@7pa4iUqKD0MgX93^57SDgN23#+M%9B>2{ zjYtu8_KG#`J6i!x?6p0Iby5xbQ`)QKMN%~sc@;z*)az&SW6uVja1cctZ{-s6hyH*Z z1!SNao_NhXWUfVMRADPZ1kbq!=aT8y$nPI+91t^fGt}rUwGlUmtTDDl_f7cU|I6=^Dn*h{BzI!`O{DT?d2&Nsp{Tk+U8F`_2lA;igGA+ zAir)DLP5%2%j=2N)Ug?3%2oZ@sqrC40$L6K*A7ynPaLvXV}7HJBQ?IB<8=-d+%UUT zL1#;*w)+8M2T`gxt=*yDS-rpV-8W2DZ{B^z&8;+Y@8N7g`%qU&%GK|-e`;d-j>*nb zr=50vb2D;=*wIRat);m)4{xwEd3%|O$+}Yq4?13QcQ%L}jfls{%WG$uBm4Vq)^%>UY0&bN2YRM$4x*+4kfv?_td&NIZOiVug!u;rEN_r_zCDjqG7j|`Hl%4H{r7M=b zY4XK!Tl;T)yc|xIB668vv?(&mZQG8uCMIU@nQS|DK6jv#|2>`^y#4Ij`sC9mw!ig0 zX#bk++pZ=jG4J;5;EoKnV6-_Z%6;38&rM8B-!<9lcp|>BZd9h?G_+)UnO&}_6DX!m zEQ)BQoK9-+l?$8Kt$rOGg{g_j)&pnLidx2G!ws#VygPMBz__p2QSkKDZaZ$}BsEQq z2*N!Xl8(}p>sH&=yl(R5zf6{{Kjf2L+%J@95uJR3UmkVU=99JWzhUym`|p2ZeI&A+ zF(DWA)#Ujf+WVymaru|7*>T}odUJZ{U$V;mZrjNCaiUkdEsi!R0Ej4-TO%n^7 z3qiLIQxg2#Dy4|1+W*pbUw-zU$-g(PwK;O_>aNwd8mLT;pdmi>wB?sXd2^c!fwzuO z5`1Tf9YmoRrnUL+-)m;N@(q*Ew(NJh>{1YAW0xcE83VUNR*wGxEBtnN6Wk8=l-dgR zT{$FXiXG5B#FNA0^1HBO=>)pWr$JQQROKYFr-5IKxdU< z06K;cNvw1zKFy+p(?{*4c~&MK9O=uEP4>3to@>lG*H&?jmVSm=e6}h3SYKms8`X4*_ZXkkLCn6xZ=i;a4Q4fD zx`@VVv>K^KuOSA4Ohu*LZNO+{N|#OF0PSFpoAG#>0_P3iNJe<*v;iqopWtC992`5h zdK>B1u_MOpxEnk>#@8IEzC0!0egU4??eywx3cgfteE)$F{iQrA-D<&d`$g{nnc(p5 zdW}$FtRFYp+sbHGPp>@qII!oJ$DF?j%zFeghPn~&J&L1JZ5AxETD-z`$%m5z9F#b# zf&tFyk1T8#Ew?8!C)nUK2QBMwW8P7q7^V=&PJg-6ZqZ60N$j70udTEe)1qYBYFsa< zvE*B!R(K7#cgAJQ0i8;Vdv;KiURZ7Y%t}J+kV3``Xbs#W^YqGq6BiqcsO^;vxuNmL zR;nP`-tD%{ z0&=RL6?c!>Awr~-VujV5w?GZw{mHC8qXa`pJ!VL9E*arFytK-Om<8$EMavs=D}LIy z0z_pRLhLwzNApA0O~eE0;z~Q_Sp>}^QcpkhIGtdibmm$_JZ2s?XR|+jm``4)+#1I3 zEP@|1i~tIK2)z?F*E}4TPL?5fMBJvJnr^lZBQs=7ouRv9DU}LYR%Y54pMUn+t1mrR z;M}vkNPIpIgwNR6_spCO&P`EK!K<%4Y~@FeIOw#R>6ew!)%myQ{_+D`_&v)@FaE8n zvI2@7$gdl%O4Uz=XKWH5TcDAl?3&8txOfUBDYKxarEPMg zpT~mQawt(z6iRw- z_+?kmvo4qFWGZr|7S-5zZ)I5~1t+t-s*g8YMg^@#&hIXyu_h(5J70p$WutUPA7kjw zyYK$E@VK~B3H`F^zI=^H#%-+0rNqZjD5(WytwTIH5$GlB?WSd9Cs8Qz$?>Jl?UOn= zZda;A6Wm_ZTi zCzwsWBCSqmY`=W0sV*;)Xn%fQ%ixgQaQGw4!I9yKJK7M`-&RnNMxjK)mD(1h93oJYi?NsXB=!G8=L5Fil&WsmD7I%B^qWF5= zUC9U40?u$EFmV2LP^dprnS=fBh9oC9iBE$H8#4g zRUc0q=7?rbhVt`5Bpo9YZ)w|TVf>K>W^;OVmMfwbAmF+q3fw7z60Q*x(P}4po6@^$ zJaZ{~#De~J} z3}$ez*Z~&%sa58I=iDhS9$<;Qh<{QpdW1V&_dDr&>LYVv%Ffxu4jJi>=nE3EY!;h> zCCw|09GIgca|aM7h|@D{^@nAKOm0ss@2bUpD5M}!ixsC3D-MUZ)(D3u@SJMK z^zmd&m~29Pc*!cDN9>+nIn>#Q5+%ULKx5Z|Kbevfq}nbrwFP3Q1D_rNC6$yG%L`4w zTZMa7%o1(};t~UwR|q*a2i$4=!7-p;aFwyp%<4(AA$kw)NCd6dqWKX?Hu$(6aw=7P zIsED;9ToM6MvG@ez|&S^<2~$S%h!>4>THNML|?dx>5SQpby&3fZQI2_E&(lr4?c4F zVmIi~D3MnI!PAI?2eii9=9JYFgoEjx1O;F(f|2oWe`jL7c)88ekLBZH%s8rw%*lPKbTxAzA9-aODSw+_BMs6t!^S>nnFpW??NY}2V2friK~L8P9z zeBi*hFTeCR(pfW`B6!}o{GY^*LN2o~-*lgPX7wEXt)-<&55*1y^4mo+ zI?z;>Oo{XK^!5aJyPv$~b2G7i@}6{FKMB2AMY*L>WwXvM^IK({K2#BNA%@Qr|D->$ zzbKSKeUq<$Qde#Ftclhkzg-X_OihoWDkZPCBS9`=iY6Ee(J+e2n@c?z<(}-`3Y}I6 zEL*jdODpu`b|h3JoREup6k>K)RX|6jM_;RdQ;`$9EwZ~Vu)E@FL&~>Z)nTpW?sX}S z?AD03f^*F|jsw(-w9GS|m7a}xH^zI?Y3Zki+9S&o4z}bwPOxdf=vGQ6xcvp~v=BPg zkIQNxb1>^kAvPQLiXA-0j2KIDkQkCfrlfXQumT^hf@d25wzS!-o*jd6gy}imtmx~0 zQP+JZ`bWsL0H_`{CSFE`9NwYVJ|RT`FzIu{6O5y}^|YKapb@~1tQVPvoxMVyQ|wOS zH6qKI21)S_v3_lp+fIAQgpk-4*siEfqq}Zl4U`&j2xhiVm=1K{^A?j?)Z{!|B7w%+ zEm~W;n+%F1@*5zy(@l@b`I6J`ZUTo-2qM$Qf+}L)c(9YOpKJ5ndv=RRpOKmM z6Q*^UCB&``VPtqrln$#(%EJTOa5aQ`XHJ`KLWrdM_&`CK>bS}0#PA$&o>w}|UtX70 ziqE)-5ZjWmx~P7rkzuu9ncXa6hZxPI*5!o6MPBi)PAedCEs(jnjUm+t8h0!B;NzwT zAIIg=sZX}xUbCN$A18LmR1O-w{#J6`T=Rg(h}g65ckbY_D1uNZfsa`P7Sq{=!B0dE zwYZY@IhzBBDMZ7j>z#SCjpCVW5kkz=6F$d0=6}qCGiwQkBQ+|v-tf=ii{`IW?B}=(OCr+*=%Gd}-n-nWPWZYlj#`ou!xSvrqO? zZ!~1NbeA|)MeU)NxO7xr8SYHztiH)8zS>rBy0!R9bIvJRo?B;q=;Uy|kWe7Vhn<<<+A zTQ9&DtA)#L9$$U{FMxYbNaxPIVh5X=-h~t>WJ)qZu#kDIfMqXaPaJybBO4;c0Wkh! zulf;Vue<&F(g?d)?gYW;;8GChO+zf zN;YGFye?p?<{~OMB3!L3TygS4OO;qjN);V0REX$8E!sotuzhj`acJOFm{!JT<6a|T z#|ng5lBjv#z4rbkTI>0}y1^qsMdU@u%I%j$*q>=&3GT(Hu1;mU6pELToS$-^J- zXIsyC%a$0e19Z@l7e1UA;uz*w)$5IOsOohJ?fF&P?5B$z!nmx|3K*HcchA2kro!P`r*j&rV$1H;I05;2r;~v>^gq-SWDo#^B z{j>rtb^iR>*Oq*L=-QH(|Mv1r&%OK-5H+Ot=fziFd5KUu-!FE^7XVVJc-vOXyQ=*@ z;qyEw(An9JVTSAnQ0zb;5C{YU`Og4k3pod+N<1PQZ|E~sy& z`V27AIx8+xv)om3E-3;gMw(kn-8%~}bQGSUr5~o}*wrRkH|C#bQm@r!oo*>QJKPl5 zQsmy-6h1(Y6b|JM_hvWdTwpYZch=u-DLmO+?9pF;oyV^0YfaFjT40_Jb!4!bQ`^e@ znT@d+zIQT|N;j+ZGK**DsTC=nA?+2kuF6J6Rbv+w|LmkTc2+gei)v!sZrc+{$!7KJ z7~`s@5)KeL0-xv;S6%W65xaAsFfz?{!E(|?0!;eYD}LmBnR0>j=<;=>V+Dwv_R1#0 zAjORSBE#4lJ!;4f19qb$Y(8>5szXAL8N?1~mCc`5j&u)VhFo&`s0%l3i0lFOi{82Z zzwEsSd=ppJHV#St`@QcjEz9mEn`|0M$R?YPv9k#Ygpv?KGbQvAiVHRd(|a?;rW*I& zd+$Z=-Ilv7SJ{#!%aYZ5pBeq{oso>eHjwsy`QFuGe)^f2J9o~#bEP@YoO7PD*~uW+ z!IKTJEDoPB+)+V}al7vb`QIR%0o(rB^XcuqrrJ8dvgZJ>^9AH0fB?-Z{D>QhU=Ki! znXt|v(>hsyhr7LOoMzbUn?6r6$RSkisA+ci`=e+jz@x3eZ>usyW7_%5?Eg~KSK zRQO4U>l*UhhPu+o1sBC_{Aa(2w~fSHVPtwcd@?JA1T3_N`C z_`g>>y|ZxtOEv_cI5^!D5*Fp*1I|2*nDRbUW2TM$jCmc^%?SPqoY2$EX$B#s z5hIASP{Qp(KoV3B!Oj5!?96~*2fzfA;{`EZ+wI~w)B4~H8^`I^_J6g>@(fSD=5_Z+ zSD=;S8w(E3n(r_J@=m)o&JyYG0_>R1 z#Y-U>sY9aGM`Nbi5*``c^J1nKcqqJWefYDsL5J?-@stL99THWeO!otUE;(ZlPqE=l z;RVUeXR{OFC8;1v*hx*A1X(y|{iry4hpL3pCd52!_J=hpTDZE6^I?w>H zPtIxHU}0Zdfl|Wgr)KrFgj36|4tAz@)%eny6KHMW?5;Fwl^2Iz*3p=tkq-bS>H%W5Wx5LP#Q07sq9sU%AWv51#@Yf!Fb|a{l{g4s#Yb;ze!ncsbZZntvpo zQG^Y*_wD+_ht2>C;PlRo`4yNs#%Qcbiak)q1a?gO^+@QE%TCj+2=9s03pPVNbd)G? zBiEdRwS;V(aCQPGob?*7#R6<+_0NDpWfC(xT2bhYW9OG;-dpo=dsuI0n9NPO)+e^v#LBH z=b@saIM_i)i;@~JR$Fi2iK`ATeC{|6Tmt80m~CzU`hr7qzjpW=acLWXdJez8^!EdH z#&$BS(7H!g{$pdnb1<7G9EPje0>~5oj4dP(n7Tgn8Qb8Gb_GQhw@UQ}BQ};6^&tTV zxGJqrYSo<0emKutFdo^SbYO-@JL=Dzvr*NYb5 zE%D8Z_ya%P3vS)roffcTk^dZ#vpd@Jyu#~vJo#wKxdBB5gKfLn zSJCjY>$s?;{GOWZ=(wDU2AWv-wATZ_E#l#x4mO+pB>PS+6%3IJn?}@H-Qj>NV0~7QGrnRg}5}T1(JSXLe6xSR2WM&ng-j$QkNQYO4$ukB}+V zUR||!+DTU$Qk+|hPB!JZRwV6jE;!MgcZ}W^S)YFuH3&i9oPp|+7)NGja&_wAuJWsW zWgfj{?#&fmZ6zLz-U21J4d4vDPD*Xc;tsV4NBVVYzR3-O+tTB!-V2{Bx^+(2r#+mfMjAIgJOuO`G zsOy#SUC*!d~w5pY-Ql_l&owC|*%39wkYrLm#@ZWni zGoh%JE!F5z)RdV5PeP633)Vnq3b6Q%{UfrF%sSiCsZ-bZPgxx_Wo^KeHG%)NE@110 zq{xD%et}$XVj?gQ9-_%L$0+Ts@64U^)c%pz-+2Ai#fulYxm`(3P44XM5Qzl1f0sy5 zGto|rzl=t$+PQQ4!@+3}ft|#JcyRW`3+E;SJ3vYL0L^G}z>Ze@GrLp?REHVv+`c(? z&IFnRG-qeG7sJfXMN$%<8W5 zbq%11GcC7h1gCm_oPggGO3u}ojMxU+ z(+^^t=yW=*c8mk5J)$Hp{#M;D=n)97U6gmyHBZs?KZhIutfa+X4=<^p@SfawyeRbA zh8TyCf{NZ>i1U7y$mRUz)U(%X>8zhWVc=m~c_}onF1hD^X#o7I;~srBh=2y4nd(VXjA6}`17`xLdrozdXgRUXXjh#_a4q!wM42>StI z!VqmwZ6u>Rxj5lyL+Y-+mXL<*vu))!TMN$iQ!|tzDm20&!Iw&{=An8mhUxGm1p^Ko zCV0t=hq3K)!r8+dXUyC8r%$>on+JBNq9{@RSIL)MTCu}< zhPA^?m?nR7*C7F&gCdxoNK{8r$%yFaC*KiaY+%a_jKLv^QOXhR`{T#NiS{$+ANltx z`H0w*lTMIUn`YRQU=IE%B?`n`j2VKRBM*TcB8&}>H8OhaIYd-Vg_%1uU=14h(#k-v z6A*-EM7*Af1lw#vnk;M;_i4e7)0e9sfSqYp5+;}Ej6X1BH{}D!bO(#|Se;$F3o~ck zn>r%UGK!@K#DIx?1nl6Udstq0lKt~mj<|Q}z^h+0W|kmY1ac@4ogi4^{g4BAtsgK_ zm-m-RewSbe_v69JMtZPX^e9iM>txFsd)Yl)IZv&ZLxMcS#Fl0%ic3^ldUW+-<~xPr z&lQCMm8i8;wy3^msEZ>PsC8-s0`C%zTXmxx5RcalF}S;5e{;@Q81j_7(67&Z`;CQP zEjWAjRBdgwTrMRR(1f0y($eBbLe$1?pM3lwdx#0{cV$HxO#c~&RtU$_eb2i_^Cc$y z!h)T*CjmQPv$x+5PaFsREJ(*9|7n77O6oK2xE}EiI%J>F**kzoWg~jAc&POL+5^6^ z*=55#o>*l7UpI|JGBP~OVzJnqVS!Yw$0;3+h)%0fh-GTINHEN1vslAifmm*Kp&)9N zjL+k+SVL^qh)AwB7*N<=DT7WD{C8Wekqh`-(CH9sL?}|}wD`jsv{H!WDw$Z!Exx)e_Om5W5*y;ldSw zMmhwHS0dw9c&|U`8<}1@%o~B@#V~_XA?A(@!|}2O5}6LgdqeaSj8}_~#-LUJ+e1ra zX&Ma*xroEXPv;2n>B0;;jYKNo;KLmn5lU6MMUnu54-;TVuTo2R=H>#i zLOTJ70|V7cCH$dbc>V}aqR_&(jcS<)4A0aY5*#Uoa+L<`KnyCCbc8>|!kY^u3bnzg z!^VOg)PSi)Leq5d#ZsA0EfK3xJ)%=7lya$hG^ZNT$W%(14A^d%UMW>*iTOeFh*HRB z!{M0qA)aOijZ&qQNEKocZ%C{Jnmkw@dWA#*j!A^X`WYS(snrHZ<7&_;6cX+TTyQK7 zPbgDq4WsR45(!^0!lw1KWVwC5GqA9#mLm|#G=?$yg8^_wa*bi4$0=BTeADH4LW!pA zd@uoa;O!6!M)emf%%@KXc2I*}D;LYvDzOOvy0JMU0;vYi-8JYnQkjtRV0pm12iq&e z3LUIdqEjn`eEbW*VvX>{YK>B%RLHn8OZ?@pC+6f90Cqab$%Uai*aOuVB>7!sq)cj`M^r# z43d??j_RaSeYN3YZa1u9YMeBJM1+A-NGr_Oq;T=w$8q?$?|?7QsJ2pbpT2&*VD3{BI|SJD^RT9bf`G8ma2s z@-6l-kq07z{nI}sg(bmAHM|avIg*W_T83tDUqidyoX^F>Ji@_)8PJeXurt#Hc1#)@ zjqz^xO_hCcgcW}AHj4YZjPS>DH1Eb7h=dt?8z}|x=>!)5%=1v{9sarSDZvgTm&Qrk z_D#2va|8rD(%op9pHNT!1PCRcECZ=0LSUQ z#f~^z9Y9hALGcLg{4f$_jKD?oNYu6a2d2++#L2^5e!rd_FlOjH!(dfcaNkfpT%tq* zjJX~Y?vES|Q^Y0&>bTbq{MsXs`r81j72(CM@TCUU46z|5)H>=Oe3BWjP{;!V1OEBW zTR#QqjLn649RQi{S1eCXN>s>Y54%dBD3ywh<4D-XZfDP&Mh&nKut+F)XYQNh!49F7 z)x1xbgACLJ)Uolf1?;-O7oCT5niv)hN$d^~i2$8Hv}$bX0kYXd71FN8EP6NZnLB&-zZU*@*1yHnJ2x-q-a1?B*|T4MWA--(eTwKT z8Nai(GVX5b0O2|rDJ}`zw)ykfvtM|5_G0_PnVrqBYPv$o?+7^=oPF=i>F+ZEoN{=D^9A^(x&L`?^wO~$7$21&wOj)M|3x3ARebK2^0;9NJ zndSWByRW@9d-ey5HXOh1(VBPCg#bIfnZ*~@5dF{oYUi2gy6*AMN#&rV+2h=XSKfbP z_UwAcwWvJ21 zXA7}hKm7iPe^rvpeFE$Vd!?xdw$FX#O>p{$OIDl?uVxZup(X@75_Vr>&Qo!l#^IhS_j~)SzJfRJFAH~Hj3f67j8f1(^lW&iNUkup=11ABw*g0A znmv2jmXie)$>Fi#cNX3Lg|0!1JP?yhz^EB$N~Ms_75Xpltq(_GO;0Urpq$f{JkVL7 zmiPB}q;dO5^p32GB>RrslVwr%LtSy5wLaaowE5hM6Bc|2Kn8UR4vX~>i4fivARy3wMcU5~Mh#bSzw2oM6 z$>p->y>&@@sFh*Rq{Pbrp~eZE&@#T{nDq)^#=|hS8IW$8Tu?F5=pICj6-B1t6*lMN zfMvexyzdb;q{xECt6Fz`iz7_rhj};FH-9_~-Aag=N(o=w-z|f`0NQ_M`v^%19tZQ(aNCK6DBTVz``%zb)01?dulg*UPoyI@nU||X90TD z2$cutE(TLKo}i#1v)FF>Cw8-}!M5*(`2(#z2=PuIGa*2&RDJ5J4NiC%o5M^im)~X1 zjuC(5M40FTrM{WYO{wUJ$f*y{s*TPgXV&#mIYPM}H6?&O2+%@|6RloT^dyET4OkCf zT-e4+DD8+LwIq~vlu?EmJgFXbV8Ed=PELTodwjetPxwkYDD9UoUwH4`w;x+==Lx=^ zZQHic>9n!F67X44QZiv45V6g9<5hA)Ju!5|fSf*c;_V3{tqHI7 z_M5NS<6!6K-b0I68HKAvhY&z@i6IR*O z*hUcyqy$7p#${&bkVvGw%-qDNpzA3eETPiKqvUv9-MDOrC#fKtR8C3^c5}Yq=n&AT zQ807;J+9oi<)4sEA{FG3(jy|{sMH=hrLrXaT09Lyv^-Lz*R`wHz2dV-q{0GHM%1-4 z-eEcQG^0W?RPM9t1G@_W!3m^lQhG|1$LVk9-OQqy1F;Z9FJ`wU-rlnJ0ssh%<@9Se2@H8mx10Wk>~BwVuM;{D@uv+IYH?7{l* z3*T%#d+mNmMPox-hk%xN;n+#ffSA-=INr?I&|?nwvU`TOMn1K`@7nssTkjR+X2J1> zUU#`*x6ii;k!cuNzVW%hcvcS!J7P~dAM*)`Plu;x&Y(QpBWqH8(&b+`p}b7*4_^mLl% z>YI{G?j3APg8Jv>6Z)q|#>Ds*3~Ke-iNTJT)slSc`ww?}diiE#<&sj;5x&G>uCtBFSt;Ycx1RQyQSoyPPd<*}Qavkz5gICqxa?5D)goBZk$ z9Vq!{+X~P2H7BD6sSz`DG{QV5XJR^m*0UMdO>Azz+V>JTD~P5%-e7@|wVOe|IsWLI`py zwMgkQcQH|u6tuaz*&fqE0AV1w+AyO|qPn_azcb98!2{=J;yF8r$?rsO?soXwXAld_ zB1+X*A9!s+UQnD;AVGC75)aWMYLTKQrSOQ&_W);ITeDZA(wV5@bV$gdOHPm-8aj*) z{KaaptKVpXSeRC#KKaEuNc%j~1`O75rq$WyTRDA997&nVl|s;@M@Kptm)GwFU?I~t z(#(*6%GrFo*FVQ$6lCX)KYJUS2<)H=ozrJ4@B}E_v-8J!293cS6KCwKXtMu{wWE37 z?(RRX*BUS)D$iV&s6MOMVaB`zGp!#AW5egJAU4&0);v6?$$J0n`P~f^+{Xpagnl)| zSdp0PFl`>B1fOngKl67*J#94I2mBAc5#!6$A=!;foTI1O`b=FLGK%0ujeO2o>ePX@cH~vyrWjD zH*Va3b5l=@cWkV!`g(g5JdB zXyt|t`wuuDJ9f;)(Rtg3B_Hn&D5o*hJW593t<5_wHH&#_1oGKb2BzOW=$b1X$U1!f zc6d<10L)PbmZ^l^P37}=EECv4^dqHbZrlsJpV15bFW^U9GFW!+q<>^$23sLvRrxIb z>T*(5Jx^maYL)DI@`cY&CimAr;IoH0JDJDaqr-EW!GDw^f?S8K+kaTK)6NlSc=+Ja zz3W!k>~xQ-p(-DM9ZtRH%~M+z+V0qY?ATGz%zo>pW!s!jgqBM*7}^)T<7`4+Z5J3X zziTknZm(NvRwGXgELxpX(N9h~ywS6$qfx}C=7ikdxaEA4n4>blheG(V4P;L1l`5A9$6_J%77v8@jhiKI0i zzCQP_#!-f3_;0n0T^DTc;c?-5PS0TS?r+v^Shvs737mY$(PhVmrJwHdA$4`MCg(=p zbiUoj<6(Ne3|RR>YG!Th@y%C?$qbcJXEdtC?3(D%!=D{U7u99obUV7`>kYf?j^Sdz zW#f`OCBr60fwmrh%j zY}>Qqoh9e*bkSB>IakxsE>qQ7Z-kLzEj#$fgM&~PVW8f^S;@*>yYaq zc!528Y&Wg@{&X5!C(^S@Zk~2Ob}67+_VBt^Dn*S!tG>AsnOeY+D2+OuhCy%fxpLrE zVs2qwS80sbZDIl(*MGj{;Fs_+$d41jcTR1t(oNMvf*Y? zM_oltR^Y80xb8>oR=l_Id{{7@7o39ZZQvRQ=8I106}Ck!nimT_!bgWVl+bVCbd2s7{1|?-HkN zpe?2`)43wSr77opA33n2%BQ!=om_OIry+>Z7R~BTZ_GQ}l6|Zp!J#?VrL)+*z06xC zqpKy1q0Wr95?=--rYOc0)2lH|uMl>WMQtq$-&_{Ci$7EkeVv47`HW+G3+Fm4ZP z@AFm%r{jf0XTm>XuX+;2G%<5Ni8+V}kMlC15m)Zw6=wh*vEKi@^^bp^CmrMwHFdOd z^_iucMp>yH|7Km6UW93Jn;iU*Ge!cBUa@-~X5SMTGzVr_Wd%gzSPXwdZ@{9ictVo2 znGg;*;?3-*&vTly(AVi~?41B#htmgM`O+TJyUn-z^ZfdpQUhulnn9^QX0;O63U=!> zRtMi+lzcz5hfEpj8emcTDV2@!*L@vd_%}FY z|H>Eqfnn54GRJ7|vHvS{sW`!;8>^ePc98pcoM^ccr;nE6m2|9~r_JB@r%yE^CE=t1 zY9K0u*MZZp1`w<(YGXfZetu{os?oy)78pp2jnLS~zqED2Q*v;yV{P~1zaiMcvvSIl za_nc$!_#?e02=ajIj`j5&u0WP$h9>Yq=WwiFq0U*4J7yeU4R{o;3UVR906R#v=~Zi z?O*Ij5T=Dbw>Eg{L`8O{uJzmDmPBC+5X_u~KMIzNcB6#Q zi{E&C(xjV5w)ptCQ5RJ~LEc-FxQcN2^ZL90eEXGuyzuI)FTVE5i?6@@;+)rBethR) zp+N*#o?z;K0@$&LMg9W?tbav&;E{7jPwc%GLx`92rrjrlJPNqVhry0=u%7I`VE(s1 ztXQ|zEW5UDJs(-mWU6?Syv(3O_p3$;2L=i{^X?ta6194-x{+L)*Z;_Md81&57Pa+W zLSf374DR8KqhZkz0WAs%yUAzm={zdkM2{e4AbAd#^4ODs9ld0z-*?IC^|njaY#k$~ zd~({lNAKT{8`Fk1F!iT_)iq%B%`u1ohw{669F}-wnz;D@=l$K%! zpGwLObh-z|YYM8P7xm=b9t(D8{S{3<>kekidxVb+y(})w!_L7TUUu^jTbI1I@@avR)xE`9@@5T8|?4<1*^C1+Zsh11v|9%)bap3rvl?BFPoB4 z3)Ti$Ax{H#XymB4(~I9-ymiB-QJw7^4twXY^kN;W#5**`E4lU2wGF^dThJ=kH~x6Pq4cb7kWQ1-{0BA+0@f_)+_d%I+R9JN!o+AUC3_e!g0q#U`N`K31G|Hi?*)a zII6(jANPBt^vHN-B%zbA`h6E)PHit8H0=RTTW0h&&xC@YnAoH-jqu)~Jl%lY^F?Z}89bg|@wrpMc!LlR%_bX_lU`HWt zZYr|&6h-K_vN*|LeQW!1rtBL^v2m_{94YX%>mYpxlI3eLvR5B(o@I-?C{1_*V zq?OrM+);MFIOU91-iH|Y?WK2{NOwfMZWsb)(6amMGaOrTPj-}EX)V9S9w-uX+xuFxWt_^2^oxkmfap}ENtb$? zysA?ihr3cSL}MT_!be$}a3S(*FoQAMH`)$5bWFuGfth`9-f|gRh=YVNi4v%(XsC!e zV)yzN&M;DIp35{V+%lh67@mlypFk)S=i$5mdDt7o1pv0ZXmf4-{zMO-+YZMMy!W-! z^iLiBVsl^?QP#)Wex`LsV2sJ}goo}Z`EvJNE)F=O@=YT8MvtZ^U&Gk_~-eR$+?(eG(^l;7?FGcf-f8AKS8|X@X}|3z6mH& zI5egsn4D?QEd_Mm_q0qG;4>yT1UCv>#J;LG;~xX zWFMTr0#}dFAC}7j$cymofT!AEXP@KCHn?NR#$}p~-7G6to0XAg+`*tue!bzq^HyLi zj(__M&eb$XvJE{uzba|v&kke5Oq^ec21O$ZDSzG&f)hjsPF)xHjBNy767*4!@+`zJ zAs_4xsG<%cs5zMmb_QKpdPwPRENo`{b8En}>w>1N2R#s=>Cvvh&Sz}l0ogzBx2Q@kbPZ-xI zL?W#o28r`16~*D#<7v1-355VV*56-FA12Dp7Myx#-QJ&MGgvk`( zp{G?y#2PJR3wQu_7=;(Y!UFD=h?F>Ki&3vq%GC-5IN>3%qaJSU4qkUKf>JY}fgPX* z4Ua~4|Ncg1YXzH6ttkz2i+w+afat{3{NfwmI%i7CqfZ8eg?X2YNBdXE z)Jo+;$tRMQ*uu~gE=eOC83Io?Xthd}L?Xtp;+uQ!#3wee!2F^JqTsiBue=shn4Xtg zlXQJ`a1ZPh^w=7a8h&*w*cs@`stj@38^h@r7$DsM(5H)3a($^whLFl{$?y%idpfBT z(o10A?19V+;eJ=gf}MfZgrd-6&WR&z=~#Vq_zN^vNlaQ=bn!SgGXQp4f>!+)QdrZf zhG8)%B2kDcgY2)yUvs(@cYkmE0R9}*DQfQqg?K@*Bclc%_phPQ@f0c(*hy&WA3%)q z?!1~1haaOky*xdkKUg(lu?`*c+|j}1w+1dhs%iP5YDv4Ftw&T&NMdqnsHvUjMDD~Z}{6wLvr#JYLf|J52FG9D&3gWyB3#`T%+GJU?o zOP`a%lJwv!hEEy-IULmIzTAK}g9RTCa3~3=a^*+0UB4_m7|M{lh2rIKFd+Rd@hYv2lRcn(y?&XQQQ2Q9Y7; zH+bKlt(x`djLCxNe#aqDV3D} zW9tv#$#b@W&)Eh+Y_dKAx_Bn=$l&F2)`$Fan{N}HPel7ciZ;CN4w%VGD(m%4KDa|C z zXmXXnh$$FC^37G>AM{S6_5*WJ&Jnj2`}*{9V9d|NVCP7IQrsDE#PQf!&-_*{j07|G z7bZt{w71htU4}EePe(4;LTE&qV3iOTbFwY zt-V4RIw$Wa3iR(~u~Y&jF#GD)%MQoY^0^A5QABG`zT~j{ z6}uEcXKS+8mDBbo!mF5g%mJ&WIK8>7?4b|{OjuWybY=Of)2Xd;SYt`h(^F9$6GKCg zzSuq6J+Ft9b@GjBv6h;CVg1W?x07(NbAEMjiC6{ZgI%4v|H$#xW5Lddq^&*o+QKC+ zF;#fQSW$mR>J^7?U$RRYq4I|+eXrize>AFc5GI$hYGcl>us`T97VPjv^|dK(-+X^E zxmgM$#00&)l~vKvgE)|T4A`j)TJ^$)(^owk`?*HBLR?kow`7w`V2aPRw7Bbg<7@Co z`e1&@YR5BnlYt%2y#6++5tC3l3$A~;$T_NF7?$i54>0S>gM$ZfjBK7XEnI%~VEF}< zb@1TwLwa(qYq|J^t)q;WHC<($qM?asv;v9I!Jps8Q6*RSTu8)dsOnk zh{!19(h}lsuUc%M+7fx{PK5V0Qaip}d*iOWyY#ps?{7^*^h%ug7SU@&oc^NXghQmPzY&)VF~~{3K>(*?_zco$+(RQ!2kvsyAYFC#+-tJ z*W7`BWQN5-Qi7RQL|B(I5qmac-u@SC05ZmHa}$XbK0u>rnCJAbuMbY2N5rr>O^2xA z0IZ%luRN1PI3U0eh=9%sIeGoZ|N7WrI%sAE8rc8$r-wiMCetTU#t})m;=@)ez;&IW zJIN|DAPUSI!NP2URH)IQ)RFTm+;=$b{;!YrPyZCw>zO&k0UhyZJ*A>*^g~mJ0oClx> z&JqBc&AYPQL9fzcsJUbhZm^4)yd-zO{V&?s!?-~LXw}-M#nBC(q%(D0;B#xio<|W1?v8=mbG8BB9!nG` zwI~6+jqpY4kWjJE4q&D5XKaHBAJCNb;h;m%+eDt7;3?}Op0y2mW?j&?u5la%d_GT- z)Q=7h(gE0+7+F9M&KrvsE!e(&%dXwqf7rDCz4s<*ITlvDJ*U3D4lHerM!jw8mdAjX z*I#>?OeQ~=I1C%%avU8Uo``q8`toy;NI1$?IJO51*s+L3CKI(-n7(g=M?!Ta=b;z- zs=ln$xS%6Jt%yNRNji6O{Wo8KyKU#~$Xe+DrR3iI6Dv0^x3*rga*6ZJTeWhId{WQO z(IP}599*k zS#iHaX?z;611W8K3p_pcu3ENm?TS<0r4nXaZD^R=<{hi7t-oAiv*XmoyumJk;UP9Q zotW2^n;YcpvdQ}M&#bN2+dBr8lj!X2o8svk$WnNyV*8K3Dwe{i^i|pMlXLVCW zYOV3fV26WfbmG45;@fwRu2}c2we`}~D_!rVlGT!t#?-L$JAe4r`s?Li*qyt2W#h)% zX=A~TQ77V7g~c4%u=-#+AIVz0TRPu3-_Enw90} z=tQ*N;OHD)T{`%1sZ@hP&L)#n&tKTP;M+yk)+@H`c8e+M&>*NrQ0p6ZcGaeDKeb-C z>hm4mKBpF(i7H5M8|-O_JmIi*!55!dZ*sCbvES9>(pa#=GpJO|hF1T*_FsLu2>z3>vnkBU;oGkluG)VnG^L(L0eJ1Y)6!K-tgXLYzxs4wV2ehf8}k@N z4MxofJ;Tjo`*+L1^ekPm|Ds=}-}dV%Wyu{%1*fSw1lT{prHxwm+Va&Fbch_)R&nZa;2b4lZD`@504Po44GK9}9Lw zoP1J{o&8#HYd^QPUbn?5E;}|nAvJ7!=(s>Xi~L-HNzDk3YYZ~AjKXXQ;?axMGJw8i zI5RMUAZoRoq1Q2+3fv{bB}!qve7Hy=Xk&JzG~`^?s(Uob&bE^4E#-mjW!^1?SMq{3 zvMJuxi95?<_7w!Lrw3j+V!7 z&ko)s=Ts6_PDz51KSZl*Ne?X)v$&o z?ld9l^ANc{20c=jU3}m59BApW@5oKC?LFdQcP!Z}RK^vLCiviF`UVl5ODV0d$uFxX zRSb}+8j+I7pF#9UYh^=CK^eZ4a_CH0?*<#q(ua`50IhqYUZoS!IFz#bn!>W$g7VIa zMhS~&fCC~DLCg*(oMO1MzO^7MAv++lGC8LxGC9jXsye$^F(Nh*o|)-%QQXaAQ0TZF z^xFK=?4anxo4&chv5f`g!oDFy11qzb`X~NCFs)LPAD$R;&OP|-jo>pkLeAVsboXs9 zt;5VzXr>i_8F~FfG45WVLFk$5!52J=<1+;G;ZaU8BT*m~_kRIAC1vs(NEIpfLqbpA zh`e;CJUM5uwMVDW4)?RF3(HLNQ&_GN$q8aIu(}L*ESyH7qSaHtxwZLal(GgXgAb$i z@E9v@{}7mP;@sx)I!yO#TC+b7?BEpB0KKF8Z)80O5XT1S&6xr}!=~Cs!Wg#oq2t5P zrV#F)^#IO<{C~FoK^YB*>3$mk2)`O$iu-FKrmTwuEpglga81}Vwi86e;hjWGT^I1| zx&WX+Kx(xCdlVOpiW%%oj4#X)YfoE$=gm2Y=(uvJ7O-Oxi#$g38ckPT9%W#J_ecp2 zHJjewQ{KV_&lUMFxh5kvIw~o-hBAbLXL}c=B0f3F-#ZvP8i}4SQ49ct^H&>7-%&w7#wcXS@ z8j}W7hfo+$MXgFoA@s?sYZmAgdKhvy(oLJ;Jn)9H2QUR%p!yaFWQZLi;8f$VwqC)-sV^T9~yEt;(9#Hi4)~BVz z>5UD_XzOaqY3ygyB^sG z+}}ScIlH1Hi;9vh?{}vRZ><}NkU{~YL7G z)mPHpl-5{yt+mXnzcs$I@_tWkq(;T(vfDYde7U5(qtaK-uj{VxNW8tMGP*4)qmRWn2?*$UQBIZKc<{>m$^1CWdMH8k38HR#wGqsE*!L7`8<& z9>nS3h;k+9D4rl9f?yOUUggD9L4CBEy1{75={Fh)XN_?bSP+p!L}SE!BJ!Ys2?2L7 z6fv_4W2WLY7?Jb?x`qifM4}C0_nSN@sJT#xsoDwwwTRM^=A))6k_cwTMkdD-aU3tQ zf|)6Xji%D0ri!7cxpRW1+Ej>fG(OMFbUZ4iqAbK-)LgdHJXe^hD$K)R$7q5`59S0l z^?;7?sS#KQGgmJ)7x^KS!pu{Knfo)Hj^7iav?ie*(FtmriwE;*Mpk%K({Lr9%f zKXk{eewb;$H!J_5Rx1b6@29r+4@-ZeEBuBc8)0-L-%F!OmE(PG z7Wo|r?tC!9+8~_%n)rKPHKaPWlwQs8Unl2vAqEACqB;$qQXDcwNv2lDD)~LAZm=rv zOb;cwhaBBm8%fS}tIoPrpLe@F<7{`87ptpSqvA3;tNMB>mBL;|TSimiIcoV$N{Jh@ zEwryGQ^u)j$vtmSb8seJJ!;@prk-m}+fg02qb7bUtE&edsJ1PUR`D_wE zC=(K|Jr<#aO~E;(pIsN|n_6eUFb#$syO%MQpd)y?uEzBobUL8OU~&|W$z{WJ9U5ZV zu3h!eZ`{dI?8$ zf@sO$=631L$GMYUwOzYLA(xMRdGQ#qV<{?X5sO&FA{McTMSi-N2$Uh85~39i*QWWb zqSs%mNH|O>_7HMAF$}?TQ*?SAr+R5oJQA(-g z+#5VjyH3%=Xisjh@hXlx&|G+%T79#l!MmlvB<9|KNsv^5}p2~&3;p1+}Z?HbsoEg;mJENP;K|s6phg+ z(HfQ=Ny77Vf_@&@0o%G0u?C`$lsUL@jG!o@XAaWy^K!z&L$A7BS-WP{zutZ4m4E#G z^;chhu=5hpRdh%{!Gi7O{v$EMk$LCZ+5y}Kr0keb|46(Hp{XkTN3m$!xe zLa?*)P?Uin%P`S(JR<1mhN1?&UaQrr#bRN9e;*UrS&@Pgcq`iMJ!?wi&*4$EGm_h z-kP7^N|%XLPi}3I-#}1PBpa-)VqnvoE7Q)FMQ&^@y4Y72*j^Eis-?if8ez1yRs@KK z>U-NVnkplu{7!>Gt`IbIwIs_$)Si}5c28PG>WQX`fRck<- zK!p5V+d`!<{mB${2NE1%QBbWP4<>!GN zprA;npR(!>?xP8wx^6=5wI^?Dk3|w-=dE2tRh^)p=p=fut#XM#jCh?{f&<`*10Q!^ z#@~_0&%xNVpkF&r4zuLmS;QhgOTZUX(pOs8)JPo|mk6zq4EK}nly(a_zfA#lgHkFa z=V$nOdEdNw)6dT@r=XM}(HI^)AaqJGqv&2{U3agkO1&0EXw_i>K{p?jsGQo?k5EnF#n|Kz>o zn)q;hxc6?}j7e{1ux0CuKgjIvmW-0)J(XxsLw|K>Sz9MZ z_~gbCUKdy-K8EC(LZGQt{kGqE2K;m)f-VA2J5iPRnk^+zPk}q%el>c?FB|mLnX&` z)!ZAQXUPN&7%DenhCWKJh*>gwu#5@Z04c|0DNG@#XZ z1zKBMi;Ig#_o!5=jEoGCNYvNY2M#?R>=@yzcs&~z;*Jm^lgU6QV5h-g$j{Gb;^oso zV`5?%3(l0)l|G}HH|M|sNiKpBvR&d9~{>x7m zEH%l|08;rNkI@)?;q!kkUcBgQeCIZoOQCH>?E~Z3pi^=N>(hKZT(@mr0~&n4VgJE9 z%~XkYgx^-2dhev;)}>1qg9~rie&l9yb!Jvr(2l>){e0w>4QoJvo_~@OP7AVY}r?vcAxPrZ10ll zM)jz_{r&z`?|<@@S^EX=zxmGA9}fAY)WzO9wP*GB-!5JH<(dsUkKK$f7?SH0tg3)J z*S2li44Zzpbm>0(s}XsvgQ5rh{hkFbzckO7g8WAAFY1TaU!R}t8xD+~pJMnc5M6!5 zo`9T^Zssq#x>=MwW1+K09<6k6vKDv^T<^HNoQ7XU11;fqR8@t?q_T~Y|2J;*|2}!t zoGS=y3+G~a(|cYO#RV+JfBT~}*LMe1>SOCqhKP$8&mZ%DyVwOT=I zs|jc)U2Q85Mo>9`3jLjhLUuWaR?=8}fl_o;C1=1gEr`a5sw+}Ywxl1di`!iqzM(nQ zsWRh=5g;ABYA8<3`~ZVPb05l+UAli(o z>gDB?oSY2y0AR9v_iiJOIP`do&Zv)r&>S4f%E|(WfM^4H9@Phyy~iVpMo$4plai9| z-n~mSendCol+m$_?$6H729suMYrA8|4z*g1;6ijeJUr~`>I&wBP#)|ASn}hKKc>^^ z<>lomDJfuoW@ctrR~OMU_|#}LW4&77+_&C(YyJB5;J3T7vXZ!TbaZr4Q4ujfa0pBS zG5Sbc7h(pP75*KU(5URGr|vU;8d# zfF~dSf~zj>ZlsTUG5evN^zOzKm+ymGM<(I&MaA5%0{1VM9*?OYb0D-*sf-MU5j^}` z^FtDo5(?UYJq|1ZM&rFNTrXa9bc@V%v-2G=3UDt9!fzxO_^v!z#xVyK@#vKNkPATt zUBe;(A_c>Bx1)}H`1!rQD8Fm5sX5g>0)Qv=sxp~ddr;u(@%X5w>( zH5hbCl}4i-Q#4es)#@~wvGaz4$D~H7(rV>0ka_}`R3sL_8!C~jbqIJ` zqXxZBqflv83Yk!$)&jc$m~H0!D^;j<{N@g#BVB#x8Vu@x&e zZQ1ITRIz$*R_~+=Nl_w2QY>OGAi)Oq-b51Yy>}9rL&iP*V}KDyXTDyv*l|rE;zAr8An4Zwn*k zA?vln-31L9wmoe7*4%qd`SuEN|I7rqyKiH}G)Y9v(c710wtdF;r&tr_PkSw}nB*NbD0 zfZ~NNOD`RGm0y!%V`I;qI|mX&C=`OF6%`fWz@I;Vp2OjQ&m12gzjyCmaHnN5*~5nq zLqbCC+_^J7J?-r53@$f=!GIoi`}XZmKKaDm-5uP1e}8`)8yoQUAr6@L_|dF+qVxg0kK1y0g)k--rio?Hqc*?7EEevY;%bV_a!$s7v93%fx3fw zf~+8u_V#wT3M~oE2+9lU3o(?sfIjavr<(I!8wO~W*I#ushe<^adu~yYt&%Sn@%Aj z4~{~$gCPG2p~a59>+x-OLm#=-5n7Wc$M4Ne3*L3W*=hCp)FDdjhy)Y)IX;^Yy!FOU zSFQSR%h3n1B@+ZFkddswg5xu@lG4Ju3ok3?f2h4_%N+a%va2C z8~sDBezI}Zs()Fv>ht4{tXd9#qKcF8;A6MW-Weq|v~1Ap!2?&Bf0dGR%R3_vc{k1t zwAAt18nG(>@~x0;OR+OLP|`i!JHFsjH+r27OU}w0k}70k zaJ9)}48W7_Nhx7Dy@J?*qQ{HfLQ>-j+83+ZmYZ9ekifC1JfBlj$#V5C$h&^@*KfbG zYSnKxesw3ZeT>lQiP5&a+fIAm`*_u=AN}lC+i!)IkIzXUer+Yo(Z3+b_Si3MLvo5s z+d`SPpKe;U>IbV-M>2dtN-)A{^oyt@B2Ua=M8RQ6Ympq6^IU()zaX#|)2UP#w;24`c zLCRsM2nrNhJo-? zJD}0PfRFc{_Q|E{`y#O;=xeBsaSkXhx^-gp&)!|N>epYKx*6Iw3hcdQa3#IgE@ozC zhB4b?W@cs{^O%{LnVFe+%*^(fnVFfH*}nVzzH?5ha`PiMm8zstQA@Q&y<1OrYe~J9 zv=(~?*7fggXU-ek3e}3Q%PQAqUD%%l8OXvPLm48uQOJE|8-!{z$)^mNbbQRdEQX;G11Bwe!9a8V-^d4}PnYYvVt;kqzCWMy1M;wS zbae6si8#YJ01D*pbHDMJ@&^%;KyG(wM|jOpDG{!YKHs17U1kvS#PadG{l8?sUcK2U zfs#4s;<_9;KAg`FLw&l9eum&|=qr^5GsWrv$+HYwNoFD8K z<=3o=bladlzLJ$l6cZ!vv(a4$`58b8elZJ0W)I8czWZqCyIM&Pv3T6km^S)lW4YYw z{%WgPoQy)2x|oOz-mFSSU*zO;UtKjPM*Pv&iKT;6m6PeV_%_7)K0FaovKLZ*3#T6X zF8|rTH(%etti+V?YvN_MZgjUiKBXif;m#@nt;SK%{PmR=1h|xLm4c6?xTHdwDE=q2 zRecL@|MF^;fiCUQ^5JU<8TcYSr`H{sL1-?%-RDb*o~AW;vvtP-j>ZNzUA%L%3Gd2{ zZAFXK3S94S4QI#^>x8CFDU&k4_DN^%Dh~dM*X2ZSl~ybs?>=S7LZn_^kVj z)g*~(q!HOjkr2zdy&PS{>O|B(m=n-PElWxK&JVj|ZYj*iNfC0_+?4><;OpS3gw$cD zc17M$BpTQ(p3|4TlG^^voGOR1F{f3C{7aY!6+V^porl-gjbeTCl2(h4^A|C5i+h@F z1Y+)a#46tRNZGGzkHj35g;voa3g&TI%#@ooW^V%gU#>rRelYR!l5kUVU3528^ly%s zlo$BcR^;bQ()quqqh%j-9-H6(UdOB99bbMOr={?QaZt`|Q|8^cZ7!Lci@3PJYweMz zhoCSug4Zyjx45p9g$s|6o0^&mB8bDmoerCpsm>%GT#zylU)WMj#X`Yuz~WDes98Qc z=0Fdxo`?zvIQlBTn^C57M+co3?jkFnYCc}ZcaO_XRM}!)(9_jQM#7oYlpsLGOo+=| z9Uy7qbW+DvcCEU|T6z@c2ufvtxHSg-RE>90w-VN%qQai(+3yF%iixS@YEl!ENqXDv z^t~I1-q}0Lim^ZKnveUO!Ng6|!^6c%rZq6~yem_GQpkK8skAP?7HN&2 znT&1JIF~+io`-u`M+fJY(q_gijk4qT;P#{$5jJ9MOm4j5BnRD#Tlyj{)zB{%1YeLK zpb+`)Z3tq#f|<}IbrqAbWzje*F&y#%qD_!uz6{J_wNVlAmW2ymk1jJsy3{y9qufc2 zZr2CnTwy-J9K?$FsO<;|Sz^M_!r2uro{q;fS8Yr7YbO zX?sx-n3r1^8xs>q02B%Vw+q|?%*#p~#iug*gDVnuTn}I#7#oX7Nfn0+R@_V!>iZJv z3q+6KdF%cDG-|{&!{_4SqN}T$hxV6E6U1*GffE)i0N_Z(V+2O5Vc6MQP$}dDUSfSe z3_b3!fI|FZfL?E|I<6BcFmm~VRft>!$WI_PzVLO=u#`KL8z6X6P8m^EpZ ziF$kUVgbzxP|wY;2wYsO)PZh&ovHVL7@F4`nYn$PELKs}u&*@oeEqB~Sq z@wDIJ@?*y8>C_Q%Ke2-1b*I$wP?MmgIGmrKe{EeG>GIl8-rjh*25|!st}OY~h)K}f z+tT~mO#S<}xQ@HEyHBEmq&e{5FI5e>Bps3UMzI<)(oOqOb07276lr4JA1YY!6q z6d90L&(m$p^U@hyC2P6CoSdA>`LX!b{I^bsR5)t%aK^}}iyK81+&1ETFvfgO4^Hid z*KWICfzo_rfd=`K9A&I`qrIkW*mbMPS-TpMAjW}`=|4kg0-1a6K-{5g8p^piX;?c9 zfcPL~{H4Hs!xH1u2ga%ib=>_s<@m_Tnw*_zf3x3W_*90*UcQ!x{Fxi5%t{zS?$V^U zibA~caoFxnu3RFaposN!xsoi#?@#Huk{8wzYkNh9N7lserM>Y&Lm#fzETD6{r24vo zMC4#`Xrqn}iG!h#hB4r+&V@2Vbxp-60(0l{$8|j(8id8|?m0FG_xxSS%eph_Pl6CF z)Qw`n!#kskhI=BuyikjA^{EqPfB7()9V(yeuE|oP ze5#JbT|bp3v0RwD@@t4+Xpln1Luf5CBmLxh4}Ad2fXUKb1#-vPq$nuMf!41PjMy3; z-ihmOH77Ck;_^BkBAciAawNEd*!V~E)L&VihTN-aFr$+5*!+Ph4)&e^=i{CJz9YqC zJg3qCe(jX8PVcD-Eza^R%E!+8m$gs7YO;3(v!ix#rYS;4iBXFzG74&oqJ*#d6j}+YnrR6`di3wVds7HxOd_RBUmHFD2XM?b=WAy zRGld=1RVXbl%oN`gRoy5TYZ-4@OV-u4c zE@zl5uK*NoB>l^^rk$riB%nIbv!l6UX(D}UYU-0$^TzBWb1Nc!feQf+D5N;Vo$tUE z;4UC@K}%9HGW#}d4!D{ukghqpe>+1Y{Bp=S#FBoNR0uLVz#kcmQ)h|LO+w z3@RF$H9%8bT)gLTNz0@Tly3X={+O4Smlzrg;#OHnDKGo|?d=3bu>SLM`>V>G4>T;p z_pQ`7Kntj4h^OB1cnW_+|9C1}seIw5OSA1ZLJKq;fbl@k$lnN> z)s_Ahy|k{&2LCHt$0*;|xWDxz<>yj@e29-dEb~6oG$t4E2A@YVMw>k|IPWylmQZZ* zb&S2fDDG#5Zk37dJ_Iom{9QhJ+@TUj7!XQYj7f-Y7kFBtu+SpN5t)^f#sk?shOYu8 z{-icI6eB%*q58_K|HBx!qvm}C+c?f_^XzOA>`eMhG15Y!r4QZ-p4s{iD`{zDVXjSs z}{yXLRmSh&06XpM`57HCg$8ZShS;}@V7dlLN4->4$Q%rsC9K*@5ixMoer1Y z<=jV1LYocGhw(B&r3-EEgcN0FpzeLR(&^Bs?OH40z}F$uQTgd?UUON)$A%_Mp3Mka za;`~4M=eLx#2;S5Fx;le4%rT*^rR0@t2&7DD2cnCL-@+&Pl(bB-X^vD#z7(?-f6sEeEN$G-Kaf@b{_V3e${7gp{<8@uqa z<0fw-oXQsIWJK-lgaJ~#Tqk%;WUR>SSNZgXd#~Z7^ zuY?kI&jS3aQXAgqb-W1ixLwjZ?bq2fd>@7=o;Wph0xwswqb$D`7j{sIEvUwBxW5;v z=086lvgxO|xR2<^Fm>xPmxHH$O^mI3N2Q;Cz?eFpB@ZCftZcsT-MwI53wbGBAPuNE zXN^D7$6j@%P|Tc^t+$9=OdN3XaJ|2B=W3Gkdw(iwQ^lfi&*(H%lyCmT7@Yd8Y(>03 z7HuHZSljF0*i%Gocm1kO=IT?hkUQ#;vC#hTL^*`R^}((;opJu_WCUlHgWax5SyNo8 zsPwwHiT~3m{1fw{57q=Q$j$}Cavuw}mBhW?mKgLrV%E1S=>!twaQFmG7`~gFv+{o& z=xREw?*yzc{yCqqAbK5so z4PVBcmjg^*V_6^;4>?(RI#P!@RIuEOs6XZ0?~;eID!L{aAI(!U>MtuCvbo0t?u9F1 z)7eiS(ClQ{nv_lso9ZC3PD(`bX^p+ejf%R8}W&8UBBSG!h&f|LB zy79y@?DPj#z!3-mWF4(zqN1V?8OW_t3J8+I!oo&Iq?wZtEl3~M5(oy6y8U`DMmCm~ zxOjL6_pbmcNh3XQwU418$qQl}I@GZ-b`f|;2)4jl_9xDP+51PKakr^o+Lf$Gi12lX@k<#Xu*>7Y-qUioQy zqJ|S!ET+M$O9I++lN{t>imDXMKjA`51w!mB{ReJ^uV(FAa=k%92dcum%5^INFI7q= zp5>k7?5XS#b{T$|WS+HPc;F8BAj~AJ@2>T)Usx<8P7r4DZn!8YAl`^qXi!=w(2b}F z@?abB4-ZZ!TQh4!CIHq;U_by7oBHU$>+Grm+QA!YlwfNhcMSzyJ?Gm)0UvjU5D z08%ZaP+q{zN5GE|87#CL0l_?e@XlZaI7C?OV4Pl1QCJwHWnf@nr!JtApu8GTsvT6I zi9j4d+`fAda6uv?@Gdl=oq32m|8AJqpwQ6y4jdtbn-w9AI2f1!J~%eNa;MC{kYVv_ z8=wU84Cp_Clm>=}g~=80oIJ~rSy&I?e2LLl#Mp^8$&pb12=JTT49r%+$Dbl%)Z#@H z61ukQ1R9!fL~L1d0(Sn>9A^^)f0x=gKPN=4on-dSi-J zYV`)G5g*btQlB-_3)P@Mvo3S#!C7G4Z}~? zCRH6v^6+So43rx}aDhX-p*t88L8)32sNqz1UOaB7zKT3ew3MmdbrSac)Bnv$pvTW3 zUTv~$Sscg;Z-Z-dJmGZXME&X}E?aWlPch-M*jP*t-3G8lA6pm@XpyrnKk<8JoGf4o zjK3ys;%sbC(v2%PNh>k;%o)-{spt>} zq3CvJJ_#bkq|byi=8J`!qU8iyHMPpqcqRwKtoNhqR0J5M1UxtjViDQ(1!m8rbWLy} zX7J$#!J8v^h469e9G=1AzasE`f?XYgI+Xn-^2s)tgi6(DR2iTq|kP33|f$wK<}vi;k79}Fcy439jqxRCk|%V zX5>m+2tHP@f^8v94b$VosLDV#t1Ml~5-=5jG7ZWek=(x*KGz!6m{wqHU6N&XWwN4e z@VFdnQ3n2ti>#*Ra8&$h)S9Z!$6N+8wDb7VcQykbi5vP$+-iRihHv%DqspVR=10GE ze{w)j!*IXDj}fXYh(If)1p5ysh5X```128Xj0cZ!hwkzg%9|2#1MlsF$Tze*T7$Z~ z)gr!;Q=T9xDwL@QL>CjX7uz!Fo;kHMr9=nk%DLjEg{qFZhIs2`jR!lIBClG`%EGzQ zt_k~k{+cxR&L6KL?+xqG0AfBbe;7)LeP)UaPwg;SD>d@SM1+(91F|xkS}sec_X&Y*<_=Hj@;QIM)Ux-rK1F1qO`d>8 z0-LGW(;WHA)uu_2#TR*8V=ZJ0UTM5w{Pk{`ndglF4eAKvfGa$Io9#qEK0$)8a}sc&N{RN62M6V@ zkh((~IcN|D#02I9$?-~5unSI!W@~F32Au#r04t#a1(^)IhdD5EazaUoeA7>?g;ZNy zTpU+bQ$r7B0R<@wHr3;PPj>U&%Nbgk_w(kP81=*_Cnu-BzrT^P7LOO$AI<`k$2VYY z9Z%aM3?d_7(j*Z0;Ru%qm53dw&&I|kZ{#mT+^)B`cU)CN19R(}qoZRS;x9Th7-C2T zBqA+=fpDr5MQSA1I0^~QcH=xc1sD~745&sp$OdBCM)WjzO^81b!9rAd-UR$UaJxiU zjt&lavw0%5Ff!ofhkJV<1~3sJ2S{@4AW0w^{y@Oo`HaMbwWv}-4!k@(e{&WVRA#7< zATto|Ae|v(!A6Ps!bmhP1fW)c;&+GqP4b^MCe?^cL>hXDQOg)3Xx(QArph$0oiU(;RgVq?3H@^F0DKf8Lo({A16 zovF@HMXsD{1M#Bg3VnNnp6D2a%0sFk{gb4;x|fmfIk!o1>&UKs7*ZTRd!t}X4A!s) z9iCNcv51)-=QJF;`%Yeq_s>yCMF93Jva8aY2!<0T>EO23#^(i@K`CQDd-cz*s5j#| zGlnP%zIJb#gQ0A{D3RE(cw{P8yPsAgat`)uhFU*V%`l&GGjmU;96vWTebq#_lfW+0 zN1z|K8+$uUOcc20Z=%p|SkhKYlgv^GR1pZJn&_r>&H2cfr#bPM6u5Sq*}Y)7agQ4@ z))S*s2O!>=Q4&K`EIuOYy<Q@YL|v&0o8BBHop9IGMIOV9I)YN(_T#1lRlZ_9lnyjzr0+^r`_|Z ztJz>Chwy2bM;7ny`JK&DF7^AD#s!u!NdQDk_R=!QTt|q2=a`9Glj8F7_oRlHs=c?q zN!tFFVUDP95ZcwKm8q7bu-ejs^k7Wp{!+^_1g08P5lR6290A^$kJEWu(rD5+d*6>A zXZgj(du{bM=iiRQ`(V;_{rENbI2n8QyIR>4=q2k{Z3L=|Q{IBPLSLVZt%lo85!>%B&m1nrxqlqBE^02Sc!uLsM&j+V^(u#X_hKblEDbnp*b=dW<(9b6;Cn@txCCgtN&yK;k zKzbVUOwrOT@4goZ?$H}9@L!@m%`_PsVSh6ujR~IG;rF9I^?f84LE5CmuX;=vu=Lzk zP=RciU)lkz0XAS?Ty3+PA>tr{9!g3|pk1IZHjecj^gvAbGax*ZFo-qCJAk-`;~1&e z0a~64TmmSL;6aMzW>*mQuihHa%~0=li6~%=AAA`o63ErnRUT2Elim9yhLB7gEI>Q7&VA9K7Dr12(fd*zQxH1qkdCdzyEPt!~m>IO+7JwgcJ{PEfOIe<=@dkSEsD9(Ti#8E&IB7}ls2S_48;t=Ab+mN}P<%`_A(eb_ewgKqU z$)TnY=PElByZGdh#t)aILQwcwcKVCYJ-}Yt812`ws|n}l_wfM){TZx-!mI>8qEPzS zm0udx8{M~^j!w|F9+kyJgO?Mt*=px(8s3Ly*R9vJ;ze3En7)4a=m$>r??<$1YHF5j zwrd`b5%L74HmxVO@ccbxtzTr8cdCielNwiSQ}Bg6XMxXy2I6zsxrEN&XKkBt6^o_0 z1U#0QX*%m0TMM5P!<%0xLyx(_H&fmeSFb~uh~bTq?+<4z1-EsT0>_THmF!;#7pv`` z5%|ox%%y2%51wuxuKaH;gx?3k74nso-JQ0C^DNS4QA6QE>+i_ehl|tMt_4|E<}j--mh3vj>^NVWa2p#VS4k=&tL^$OmvaLs0EG21$ zPa7%l7>DgwFSGpL_pv^ovyeT7O4ht4juZ{H0m>K`yY;@)1Wabx$&0tlse#PnknZEI z2Xj-;Xvf=|FN2#|34*(h_eXA*7ihWC>L4gqJ!{?ik&Cu1UhZc@^HF{N*sMKSr=I(N zb0*c+dp2ASV_NQ;X>|w32V)u=Smy65P?yy7DqFZLeTTnx>|+(A>x9jYjv*po3vE^j z8rEKdlNYzi2U>nQl?Kh`Em09R_NUvs^x*y%F`%>Yc2yVa=E&B`S0 zz1)00r)1UCmO`Jm5aB7Ya~69^>grdBAE+Q?D(lv|k7ukgZC!R^I!?3Z z{N29<%sS-EbR%&S$zvR!lf43g0L`jw_t zL_l|XpN!M0t-IKEdrj}p#$4)xrx=Lyq$C`9UnIf1Kl8tToYb9bB+nS9ZtES$2gk2P zVd8<{zp_?>KZ{kV-ouMw=c}t5b2w%SM;g*UFF<0HYVS5PP%Gn`P;kSE-E|UFC7904 zTRt@%U?Cs!@5KjiL!>l>%&cn+l$Fzaz5RUpp|A4k$(0>{mnv)d1F8#I1TK|6+H0xx zQtUt;A=uYeeq{R+;k8FEq|1}#AxzlHLv=bzMl=Ua^x4kBGT@k-laqHUPaPL-6ZPI_ zR5dpT_Vp`g=i3DTEyMZ6cNPEj^E2uZQ%H#E48k3y%D+1wVl#3&t;8A>GLt|GD4#f9 z;_s`il@&|P{OI&FB{0F72xAQc`FF>nb;Ux>cLGkoRt z<^~{8^SC>J-av+8jKpWax(nsWSE{P2Qt+n7yK?cCK_rza#6$UKdQ zi3&`Wl|^kn5$oJK{S3Fij+%1P_S!ZFxzZ>_Xa;P9P+-6)tu@NIgmxI^{&9F3nFqDt&41AhMKT zG0IUrjdR$Np4Nbi@eFdMJm24ryU!puxp6XLG@5G=lvb4LXNaqvh?$3?qqL&fIcX|= z1}@<~%VVU>Hu+up?oq)5rLCUt^~=ZJpC2xkwwLwraf^-xyG$+tu6emFu0|FX{X2P9 z`R`3nQBf0gn5Ji(w-RllAHZR@Gt`6G>4R0Eya#3o`SH?XQc8b3UQkhlXw$LGN2T1} z7GjgNIJrnvCSf2V@Ao_NTM{;#jML8Ke*aw589u%G!;E$0Y0*J$Lit#lT;~3mjP0-Q zZ|A$OVVsQz4ImdzovwCyrZQ4Ip}N9Rs%itF%$;3<>6I6EWaF_&0^I4p0Q274*>ckrHFbco`>2t)b4)S?fYGFil08?qvhz zrW_UKXCBtLnr$)8soSVBZnvFXV*PP87-v8;AqvgavgRbBA{Tj4L8h>3d@XV)md#Jf zlKS#ZedwItj<2D}vdcDzl7Gp=$%U_llCMJ!cZ8I-HlU>EtcCeS3Gn@VNEE|TPvL%G zY3}P2vJ32eEM6Aye@2+%3G4;B#w>zq9VXKMn+l-_ion^$2~-xFk%6Nb?uP^7o0;dr z1SwCzV;2XplenyiRW53phq+V8$;Y?lunYyojtQpA_42V}E-oT6Lt`f3G=q;a0M)t| zN1P2-4($2?Xsaq(U@B$5Z+m>UdI zxCJN_p{cxtlVpxOCL$vP1CRs66#@C;z@ULXR7h)t)Cp;*UZIg50MV2Rm9}%uQy7U8 z1*U~tL%j%H^G^XAg%(Y7)qqTfG(fuZ*PNjTGJ`SG4NFW+6hZv{%h!+xL07DdCNZpaA2PJ{7A2+K~9PNYWB#Ez!5cg8#= zJDY@y@EMephI}~-^thD`jFIFY<)h*UyQ9Zqzg4-v)88NIJe15kksm<~1@jKW!KUrc z?;g>P$gn9Qrx;smaMjg{W*UQeSTZ@{-9IgxFrQ?lf@fHYQ|Ms`ty^h$k6X+QrhjM1 zGGPL9{#sg?Q*opk*X(!rSgll>{%R2kVecEr=`;E4RtmA7uK!7}dBY$ZG%fs}N? zl=^jjaSRpkfej?LU}TuE9`Aj=RMq0*lfhQ5Q1v36npcxg#~r{cXMGa#F^i1QykN?3 zH>6wuH7I%rjr{AcHFs)$<-W^m6)q%Py-IJ3KANR3$_=i>5_d1Ki%`}jR; z)6XliU^-^cRYh_7nuSOQpYBuL8Dk`{SLTo}-P(_2qeIj5uZ!9j5TUQK-|yd|2DNVb zZ)BNjjMBpvQ1gw1cY|~9w%T{wdzKeuBP~&bb?=a<#&T^5 z>%q6iom1oc08U%3-#As&S}=BdM;-_M$$f)W4`KNHEvW@P8q5NICVAGiCguRFX>)Fi$4*16Qq1K?bd1)s*^6qO#4_Re`mtd464N%4+Pvu z1SP~dRog}tk%);XS>iXjN7T!pio=%m)Z>uQkbrh)sf<~>y!?aMwf6`IZ(4{(wsUp( zConY@Tx*LdX=9+51Nse=q{S(WaNzTIqFs|rhiA)bcExOjc;tmgktFIcAlih z)lY~tn(s%s%G`-n=>+V)??*Mum9MnPMBx84^ms5`d1zjx_3I5{`VuKaA zs>XSbaWoaTsMx@u^ zvd!zs>a9S0z~Bm@wg`8dS_5|uSnB4=kvyVPzf7mcmFw*(PSj!YtF_)+s;h+iZiY__ zQO$$UJXTjeDhkPglTjfx5$CY~58mPlTwW0ev^e4VWs^PaSjUTo1aC`fg0$FbTJ7Na zk&B#j=%`DuTQ$c6IdfsBp^69+%IzBE= zI^q!PGhzUgVpR09=;mg_`%Vhtme%K~cK%VKXbFEzH|=JxktOf385YQyo)Y@%ym z{hG;9D(wxnB>cEUgnYUJRuYQFQdL~R)LvaN;{dBWaSQvm*Cj#3IO2!Wxku2Mq@w)a zUPQKEHQNaO6m%O85|p^}XBeijlEcpE?6xAQZ^0q0+q^xd)3czV+1`ne;-;$7jub<= z0No#bg&~6givLxtoEU(w?dMn&wa`#G;6D~(!t7~hy+wJDZf|V#c00Ol!M9*WE)qJ~_2?m|MC& zJ!LZ!tg;Hj3W>-g-6uM$ib89EC)1K(fK!|P!Ex-Y;aG4yUZlxhFN>3TXfi#lPs6qb zx$VQ_tJ3YqrKO#Up8RQjnXO$u8YNcPI?m5Q#7J!^(TVjF6~T$6&AY}CW*2$;p*lilsxTaawWiM$GAO-)}uG8=eE2Vd)srUO z(s5BL1H~R(Jl13bG*+;AC^F7$ywY;e(bH)^2FPiPRL966weFa_LFUgwJ&I{?s+n=z zS@}|h6Qv_%gw@4nWIJa|&K6o1Rc+pQ(;FY>I2ImujXf-whKXlDsDo0Q5EXdX+7IX#>WZB!cPlau&@MG6+r z$|m63!^^)88qPt+xJzXAq?t-+JD++N%TC6K9dx2*^Ae|?Ago-at9^!8Pa6BmcgOtJ zU3wRVbqoxk*Gd97Qsy5k6)m_qVFkQSlIp+uf*5?ol-BlkqID~B@Xf}b>lzR*kzz)| zlNrOJ{;-55>SKd-r4RtT~6G&^U*H0)P>t|;~@gpPwBG?IqB)>ku;WaSY1gKzry4A zidpyg=k%^@Ll_6vNUyS{rRS&i#8YcfA!UO=ZF*a1KCh8w?tB{o0ZOp3mR+WgN;rZ};uHf+)dPszqVk;$fxa%@gGo|NahcsilgBCZ*%Zd8 zC36&GJWt%4?N^!&cWiL~nTyPykSc$gi#Ib^a%N#h8Dp_X2^tzGgBCKsem8UC7lo;5Grnst2(~XLcRaHqkd)ggi zp*!#Aqoxvy8>Ked4~+>6chTYV{f75v^F+{fypgrY-W#7RWb`N$gjsyc+9OXWZHB+tW;k`(_cX9Jo^2ew$u8MemFnm zbk|m%MdMlNbGy{hcExhe%)Jdp>OY`H&c`XO$O#xZQ)Q$S+L`qbWd# zV?M>iQgBj$4_^-8#7rUO}Si%m^SCPXd`(xw2 z__wo%yy29W!9g_V$%x^VWdi<+(|x+lT)(*KTaSuJrT&xF-a3uNu6?B{q49HT8oA#u zp29oxe{UAeSr-LS@sB*Lm)6A^LpfwEqc zgWSsg)HReJze*ZnyhXO)*MojF77FN=SzO~8p|YZflzc85>=n`HgFDxkxrojA4gH$ngVef>Z9eE453 zss{lu{(LP^|L3dye>LZ8?&TW~dy>J}#>mmh!C2oK9tIG#HL!q(VJBiB`mYEN4?GOL zio2aL5&bWD0}EqACqP=+*}&<)vLvnbO^pE=V&+y(#tuaEBEl*n#)h^=|K!OS+n73; z5wWtfGBE&>>gGm(6eBYO3m_q3Y;J1iM8wGPPl=GZlcR#MgRrf&ovn?rjS~?EAXC`Z z%GN>IPTvr4VG&~&b3$A7u+3m{|>XG2SOKn55f#|UuRI*>3B2r%$J{$CCh z9ALv36cn)83ltm@0uu6{00j*T4Fv@Q1qlfQ4+8@W2XK(k2#D}-2>-gbW4L2#y5``~wIC85k59_uS_fP#TT0Gb2? z3#ib91ZX}eCAu4 z4gt_G7?@btIAr7$lmOj+tZeKYoLs^pqGI9_l2Xbls%q-LG&Bv3j7?0<%q<+9oLyYq z+&uz=fX!XpwAlafFCFDx!C zudJ@^?(H8O9vz>Yp55NvKRiA?zr4Qvg9{i4^uJ;K2igA)7cu}B2;dojLH>ga7{nD2 zK#{?~i5VeK1Qj9m?NNU)`9q-z#pl=cLX$8n-Jlyd%)(%hvh0%G{sZm5ko}(n7Vv)w z*?)ljZ@AWg;6Q-^4-XU>h#%HG@ciw~PB`?|17pL{A$HS(|R5`LRdwCwT-`{)g&axE!3T4r z)G=iqC5nvV+(T7Lc1ED&Q4;@K$V=w^j$A}}7TR5FglqYiK*&H_#VYcSU-|KSf_7LV z>3gbnFn;3LkgR)q9FgF1JhQ2Msn5=`ns2;6zT^uprqu~`EpKfv zrS}oREj3I1YA}T}hbF7dHN}$u3|gHUvv7r`_3iG^Np(uXt;!xSgAa)bHtqATNe^rT z_v{*)YZi*`olirbEO{K|x<2VObjHh51rPR#-X(`8jYt8>xZ{h-G3`a+*&I$J*%V#g zG!|{>w)C_<$y!&J^<1=1jcQ&k-ODXmj;nTeYZ8hoYU}kPVq5xf+)Qq6HD>Vm+dQ2< zw`=&;Tckw8DT&)z@G9Dg!c(u4#+I@Ry0)=Kf3Q9;xVp%kFX$e|D;s07-&$Wep)80vV}hY9@cVyV zpgGoC*ZYEry$O6m2wHm?*G;$|h{Z}FL^P+QYobE?Tr%D$N6E<>8*MeUxllhw9MXH? zA{o|fI+%R{-R%i};1*7fv35AJ7IWZ&SVs<4?bt~L-RjPy+J#$Z45$b#dL>-*pih2& zCHFcqA57QPqc;t!xOUO($!>mK`7A5w0lW0wIm}(d+1lg8m3^_~Dj$u0@bs&>aM=ia zMY23WT;2ad2&wB?$g6)Tzd4fSf<_=zx!GLKsmt}$sC9XZry&$&Ju~R!oI2^xR#y53 zf=m87w)}`?hu?#c)%1$7;opAZy=f+1j~U{8|_y{Rg~1bD!q+=ZeAGR<^lB01}68Ig~q4-@?sYlCKik4!(7jd zTCVg%q&(T|J`;%>l*c3G-AfFMn0_^uXrUv#3XXjpVj$_w}rY_FH@309w2B7QET1f6bB^|Ku&z z&woD$actwZ)e)Biit}Xgk|l=IOi);;OUI;DQaN4UzelBB@@F;yply;nuONa3_4ogi z13;Ort^T|Wjh4jR0=$gPT00nXB%WL~5x8nj#l1ei&5tJz0q7l+a8?U<_f;Ro_n4 zhj85PCR$S5PfK&!^$Yj$+z{pcNn8)9 zx6(qhC7h0~)BKMGhGvoG28KDv>D(&nlW_=K?cK6th`I?Ksa3O`klxpn4+(%(&ZB6# zCyg&ZxE6dNU-nr&dHJyZPI*~Pq=iU@pZ;T$KQqN#?m>-c2l()d?d7mWe4khCTz8T6 z5@PA>Xw#`UQ~7Ijw(;GaNXVcgXKU>=ze}TY(5aO=mws#o?_~J4M)T%k$*v`eM<4ui z>pUpLn`{18Lu<1fX?q~XQ;V`t7RDMdwRE1by77H?SCV66`6x%vk@?p3mTQ8#b#u|W zHLq*k>;!pI69P^MJpX3rB(aZT?KKhsFy-R-C?z&|!ox;;E@6Uyw4d$-u^c1TPF9mk6 z+SFZ*97D(k<`O(K-q<(WPgM(@!gj{mrhkO(E?WI>VW!uF#BIrx>y;_~>jo(Eu9P(nj&sTSY zIfx_Qe@Uo5{~_zWi;sBeL#1P@;gYM|oj6WoZFHqGkOxT&#OQZYM1!*U&=UCJZMcus z*{V_HOlh6h+Bt6nQ*{`cjTMif0=BagA15(Z3%0Wyxb8*1%x|LOE0wd}U+4A>Bo?dQ z-V>=1g1>gRrF)2d1uKn5YMjJ60;h^;n^eez;JOq0b&0<*pTn(lYEotiDmh8pca>u%CJE4}MHv3=t=;j`RPUO!f)6z3F2KPHrR2|Jx zIM+G|Vme|vh?gR523|1cH@4#&9XzOVDV&g*l2&d+&WJ!@|)>+7R_f7afMGq|j=-1Ie$b?3r>BHE{GA=+}Z z(m9Ujfjc2i04>hUto_2X&0|%&e-KX=GGcsyY??6sGJ5i`y(pTRax08q+vD&&BEpKg z-cSNh7pfj*^pbQ7`u2D__^wv{YZIS~6mwxIOe>%of-Wp+SLuwO5Y_*0 ztXU$s`;8WwA1Hp5ioAp<*Q*jt=?Do_&37GDJ5=A_6W9d~i*jhR=5uI#sgS0wk*Dvw z8kl+vIxPVhe2*><{6r#}J(z8laMRq{DA)Jv+r^qo^2OfG_ns)Y-w%qr5L_zm3!3{d z=l2>wHC1#!cSt8xwO2Y<&RsT>6 z5wV*DJk&e7;y|F5i^}xa+oN<`zO4)(p-3pA_Nw&hPl{N&{)_5ctU3M3C*XW`{-LV- znQqE{U>bC*UA|_rPz77L<{_)T@aCW%fGA(+R)DXA{{Jr7nSPq|F^Dd>MS`tzlh<^c z-uhyKs9a=$bWrV(oD?)bUbXX-WZTdgBJJX8l!SQZE7iR;B+oIZse77Y^n+5^P5@V^ zf4r?J7@;^D+@_{iQ;PR#w(U3uJ(wR4Wls3=K?%&Bt@5p?KZ|55z$}P1zur5xTJ_K_ zb-HnskiXTx#(NB6=3)$CL<0>8n$D3IX#rV!iJy6LfB0u{DPwG_ZMRKfxM|A0 z4Ps^+xXO~w+yARkgPQec3X!om`FDDzjp%&v}s>k1lyaT{w(Esk@-(LZ|s}L|@18&tZQ@sThiaXauRkyCh->H@F-j7#uC2Vo#?a)G`DDY}_Z=Q-j(Or~p|8?+K zR!jz$A3wc3(2iDI68MJp1B+;BO^V8cPf0YyF?OF{6V`uz-;^$MgDvyZBhU=nkw{CQ zU~ckr$%r=Dpoa zug&Kd!t9o95`V~=`1tuK8>_ZPdiAZhCO-F<_?D8b$A5G-&ajitT{ZdFRF98k8$b4V zw~9(wb)ee!w~H~2N)Gw3us~CNdx);DgX2%P`nRf?US!GGDp+w6CHvRWxk+-jO|o+Em++mJV^)13w#YvA&1%u)g=-VZEJGnGC|K954`-(1;DKTWEh|2w~e zoqSxLlp3JF;Ea--M`GG1^yIK-62N5hT67!~pP0Di{EhySHb%x0!DO9m@`f-xY3d@C zvsfzG)6Z)Hb5{=}b9sR+>}1{y=thY}xa@&p+KpzJa~4K-6T86usxpRvtfTI2)!s8b zz9;9r!R(DPwD=iJ^!71`7hz&EtPQ~~nX$Tgyvn}b!@Yh}=>AOpX)7L@Sy7QK8;P(1+F0D-TEHGwUmpbyf%N z{}>1@HRGDKEZqN;la_pRIV+_WSRZ1V!==BU{J(z5iU22&EozE~?Z}H84P*VFOZ|Dc z^%pfVZG0tj&Wi?5E`>4k^UFxJ-Tq=x6yIWB9!| z@=f;{3~je!g4#ZQX0YSUbp?LQ5Y;C735sQFgH%VO&VZXLjD5+{bL#U>@s;&-+aKXe-1`vdOhL6U6fpO3aT}(kLq67S3*{`g z!ma8%qZhsiyo`Kk{oyAmA+dq53#VzRH$y-f%a$o2*<^Vueb0P5m~CqQp3JFeu4_kg zpFy`?tDozCXG{3??D>{C2ZHK>{-9X zAM8Ww6?=(ex$T>Q4MRvCd$H@UOuypNxsLml$D4 z*eEnq`R|;rtVV7v7*#44yyfcDuE`gAdp2_!wxZ-g3n6 z4LOH^Kd4m`c&mnUcsVNgCYkMY3+R!-RmUjZ4C&{7=S~haiqHkA%wc4-eae=u2fN-g zPlbj8dzZ2?mjjy)A=s7P`nlSDq4P!poeq+0Kc#|o4X9rLnkXWtNpw{>1c$X>9Q~vH z^A|twTCZqpmFL>lZFF~SWT(=zsFV46f0?_xOJpW?9HndH+_Vb2^Ok>w0XcTMlI}bVH7=9CS7*@!*A++#iM2OZ6|kHMED$I}YOJg&Pp3;TV2Z_c<3C1khSi6t{v91K>>y)VY_~Vy4P@( z$v#1x@O}4W;VbNKL|E^+ixWjf!yAV0-&??ae&|}Cex5QNzLY3JeusoT(B^! zuiD*&v9v^$xXngk*PltVh8R3|$u#Sl_p0ovEy>LegxGOve3*J^A#jJoX{|oz>wYXT zE9qKm-FfuY47nmfub9!_O<5Dvj!0DHhTC0YdlUjyR^(l?b7hhvF=rslrTme}K1sg0 z`)Ok+9NIkxc2GEjv5MT!HNBE*Eb9^^{B~)*Sk=aT4Q?2eY7yxXgvZ+L5pt7;VW&|q zQz2m(hasPOCuVbyxi$9Nf)~A`7v%yQo9ZCp-&aOG zZ?o_SC2Ff{?hu0G_tgASVp!*t6F{kUzv^0Q%$3%KgiN<4keYEFX2U;*FONKPZcui$ zc>l%qoZWb2YF;6dR;0PcD>iA{1Z@f~r+ zBL2!VKp1vL2(a$Hh}XGm$~)Fb7hR1U$_+f(IHhXC*wYu`fki5rT;S&}K>f^;#+F3ZOJ@mDA zGaN0<>@Z!Z3i_1O6TO%`g}m_ulh>SQ^c$J~SoXlcD%f*@r=gp{qP{E65*TDXTW8I$S6k77LFBkaC&A;V*Gfy4tTdV0?@lE>z z4X?$E$$Tj=d!mr)=h~I)Ui<2c+dMWvy}02+xHPs@`*og&9BEiy0#_Zb@jdyhm;U7t zOS5a{!A++;gGX~R-a+DEH0gz3T8d-AyUVoP8Dj-y&e-p?4v46vu}|YwL%m}ajlSof`nD(E#%hqU*OM|8%qJGv~~>Ywr*592K~J^bPT#LUpSBc zcu-jtOf_jyC&ad>rX?1_wU?AnMa^2re!Z>%I>%`+1Dr$Ju{{d4@wvuV&|^Uo-12e>z1QHGk8Mu1m$it1`k#kmP0a zFaH+gp(CG=R{&H(#GI@+g0zBJbtJ}!Z2~{?I|4{Y4;?`&D}o8CRRM90j-CD5ZWo?f zt|cJvqngGA|8yK`(Zwr0q}F&5(d*`t=&$^4@7ywP$R^o-rJ#{0Q&7rrvT4DwjexUNP_xfj3GJ`abdfbIdHS47sY7&k)^uc=&E* z?#i3@=q4fqjh`8V?dw$m?6ZRyfG;xzaK#i%4(+|oqi!2C@yp}w5k}~uDxc*)3|%mk z!JbU!8pCv`qfRq8r}UnYd#t$Y^~o0_0W6d_l!0|Ni_OY_kY<|>&_5PMBh6F%_ zg}^FYZPIh|R0`oD*N^7fhSxrwS^!neU;K3Tp6v28KVXLvSVc#uw$PP)xF3)R*GJ!+ zyTebu-4So|j#7^#eE(qyQ(o}+o#Q!QIC0xy$L&Lb*=y*%2n z>CbDb0y~t@>zzehr+ifp3CEyQ9)O1u7x5I+sO$A$at~DTEZt^sEppT5*Zp$B4;cPt z71^+C2Vo$=m29Li1DtF#B4!`Zg!pz^v1lNvB6lR)Dap5Ts25UwI3F$}va;x1ceTX* zO8C^&q_DMPLbJsEOu64CAdr&5?8!16xs-xsVsCoz3xXiQV$@=(Dbe3T1x&d@#`B|Q z3Lq4(_9DiW;d=H%%?aq(((c1uLb_@qe>#-F%(2Td!DrPW-uL?Ve)p*pc6hBup}>Ot^TnBZ#0}l`O~z@g zS?H+e2>y`H*L5nmoD# z_7~MEU`nKA9JZ%(ejc`^!CM7Qd|nN3vNg#%89B9k&ZVVBTZ7L_mExQGQob<@52xAU zc!jH$a_3VCqf@y%{HAs`9RY3<%K8`n{LU5?)s@XZe=+(aH?h8MSN;e~mpb%j^c@Jc zqR#Xv?z;ahzOWk~yBno8cO!A?5$lORjI-HRuYVDM9J0v}Ka*zFC*5&} zXbkc)a5L<5xg}cXqCGqj2ji|+cJqgP18NYKRdzZTz9rmNttcxnf&<)#9b{(#f1QKKoa7N~)x-+%fJG3K=zQb98bt{@mk~p4 z!dy{en;lPnj`|cv(45<{HnZLtuAcgQS@z6@S+9k&aOa1CxK~F^A}#)N4;p>0|Bbr% ztwzLs*K(v-aUAvdKp`W%mx_wm#)A;hM+DX0JN@?uwp0D;8XD>no_`qRGwWMlYt943 znPfjjzKnP894x}~(Z=slRO2S$ZyS>`iKRrz##$q9sc0q*rfT{MrG>%f2)vL2t%VE= ztK?#3^lurE6j^`uGSSkZo=3$6vmgG*evr?&89j9M{Hu1a;F*n&__AYA#|BLr7w%E% z!9nxuK=JOdOh~Sm`e-MyMEkSLPqv&o`*zK_YXy2QqH=8SFXu_D48*Eq#N1&4-?t{Y zhx>;zM{*y1r!!Gc9=UmgFCcWGwSL#-EQEU-et+tC!WR8d|AJ3RJ3bCZ3}CQMQtNgV zt3tNO`YjCsEo!_5bNYG6_2rJ={j9rRg~(?EPR_)z3bh63(|PL{>}V=_SBPqXan6IM zB%vF{n61|7kRrQ=7HCVl@wYysTQkmVtdVhEA7g%bzimsqf#&+Qc=)QeHh(UsF-i@) z_ZBmhz~9X1ovv{HcnpFoeyHVY8SedSTM^$g>j}p5)q@exe`PG(x2q7YuZ$k8hh9hTz)O^C|`c@LM~hv{M#fPL$UH8`pupx zA*5j1`@t@&{q_1kmgO^=(_Wvn-q&Ti*|hd8HpMezY$%Z;<`DiN-)wk@0iRxn2CmZ4 z%gx~oOC1(e(@Tg;|2xK|XhzE{bEN9xNQ;p(8QMO&w$3N^C?an4`}XfwMOxgWcyH89 z%rx+goWK+}rt=5nOj(a39LOG_PpvEMgf-Z2`>|d^5?rD4*F7#-8^{Yep8DzNWR^3V z-=9Wt*HXw`n{#O?w7dD3zO$t>m=V5(Ct`NfDd<%nilRO3(`>-3!}rB&8?WD~?0Mn1 zo7m#iyI1ezbzb-vb3$Bs&Sfagpul@%+}FUzIm1#4MP2xHV3D({Zm&eiZdmo0YpAbn zs5Oi_8qD6pxH6nkGe5u-Yg8l*M+o0xS>IXHZPsCmy9b4^yQq6`6tu)#E;ed09ulr+B;GId@& zuxtTSytwEY&Kx~FhY=LCcNX*fe>n9COr4HiDIb6=LZ$T^{UEAj@;c+@KooH z4f+0r$Ztvl#Fhjn%!^rxXC}edlYJdS3&B&57ovz3@F%sSQ@EG(x7cNM28+QlNIIFO zdJKBHfGKmRLfQ@h~`8dCcmtNog-Rzaa|1TkqEcUTUjxs0lQSu z9#hTM*-`BDhqM5)QAB2#!ysMX8L_#A{-S?LD*eU=tj}^4SRYPZIQoHigD?>hnfiaW zQLG4Ovq+<7`Lqn`YTOamC35(Uf8W|~pMG)u<5RDq5u@D+U>=Ln1CKri@~Vex8)Pmr zBq-U*C%3F9ljBp+zYFi248)^fJcv>Mx_k1L9IFuCS+pcbX@;IZa)2dRRuD&_yOn#J z4gJf);S3;}&!oX9cIZg(&;Bqu?=={}NL>!pI-BqLeRg*s+iZ52We*J?m{NRJS?LHjvcU0>U% zpFOeNc@RF2ddanak)d`B`gwYf5#I&V1Sa(PW6(cB<;J0~J7hbquU`A!>m{czUhB}q z``wm}6IFwwqkHRqkDIohIe*4C590E7+W1WENzMOZ?gP1iMGPExr~6A|&^Ny-NExK9BlkDta2Yd8i`ymCJ(Xd3}$+9<961nmLTA`z>qtdyD)!t905 zFeWJ^jh!|mi{-X{hz$5IE}FiHFTrG^e+Az6Vi5MK{zWyY9=QWwiVO>6`OgcRSNX*) z5n^p`i??WVN<`ni@#3i$i_Z7(@C5U^)24~qAgw>l+r@{k>3-G03|5U<4^d+G&k6X{ zNnX9nap}-$Xx6hN@r%JX#NWM%PM{B4ZofZ#6DU+4!C)bf`_^6WMlpL#&YH`4~KZ1sU+9R|H#hFeA^kj)F~AA*S}saZeNAD}pNbc;Ux z-!&ozkiE=A=|HM!z%QpdXu5fm%Jo$T4^qsMR?7O8;VPnUPrbjj%am();M-^k_@or# z&O#G}Vubye=x5j?H>&QpQsc{5pJ&X^-8cq$Jz1z;h$J`WVgG&G*K*lSD@coV5cfc)6kbNj%>)N*5DC9MS`om%Q^F_M?N&04u z??WH$?q(R6AM&4hr}HrbuUa1fnDhyYnSPb_+2`9n=4DNJu*U<3SLknBOOc#4WdE3b>w^V7?FDcvL@cy~f}@L;A8NZ6<>%AX$@B5zyjxLf&18O% zXB_TX+1sa=#2BGUoEFF&Qy4oP7=mCl{)I=FS;yIF?hS4AH0J}E z+K-dQh8=^*;wFFl84+NH&D(`z(4~R@@!!!e1f0jeSn?ifAqUdgkBX4zTy$>I0!nnr z5Z09sCZ*H*XMg&KXb4N4(RdXF!qgyxMt=C4i~F&y}Erh((BB}!g%;lD#HboU) z2xHt$Vcb9o=N_gMb0h$vSM2mNbTj|QCogpR{^wC!QB>~SRp(=n9uVH;Z@xJO#Vj6! z!lED!kOMi?zRL=R9;&azuDsSQONRWYDawv2R8gMuU^_bWK z(MogMWXzAj&U~oBAcFZx6iACn7={vu$Ov%p;ORMJeq27ucT3PiiA=mfJLOp?^;6}| zCD)+qdVG&T|EB^oEC#8A@Gt|GzPpQW)gs7I%*NG}hL1QnZ*h9Igu2%(#I-D;HQo9h zN8CMiCRs0LsYRK6{dOG?Wx5S>ZUrYxWil)KQ*RQWy3_x0>c*;Kpot3G&sX6ys4N55 zUY>?#OS(7m6{s&S$~Dk4O~WY7J2iNML1y1*={`W zd%2-4MZyXpu(S3F5On>`EUR7;^r}K8Rp1j!#kaPgl&z|)by>utH^u5po9vy zrG?r`Ttp)mqtBNiG`yiw2opeb`+KNH?Op5MrO4Nt8q0jPHq%vE;lXK4B78#vEu$7~ zHo_~6-~mCmeGZ|hvaUDTM1vf=^_r$|wVxkZK1LQ_*y7h`%enuAv1BbfhVn4xsKAE{ zTU7ceLC2=8Y@on-FHfNpO^Asg6CxQCozc2;k&W`Q9tsKF?o~5lk0@3*NFz9WWxZqw@qw`demQcb9tX(X5aRWU)t%<|ze^+Nsh`BGV!!ls zx}*!#56i2vS-$yQkoH((OYutN4$a(Rz6* zbNkirM4IpDxKVXolHP#IqJmg!8@}&c%fdac3>N{tOI;H~Q%u!*oZ7B|Rj-W_3@7rZ z_S}kxedh*KAO%v8g7&2*GTgM;e&a$Ivtz zl;YMjDx2qbS{Wks(^84zsH!G9;wI-+UmaM`3Z)E$XSO>gp3TQOydR4nUbqrEl`!U<7^izkE@~JAn z*uWnS&zVS5nl=q_s*X{um{%`XK2b4gUAsPwj7y`G(nJ{QPJD>%q*Ont3L zKZEu!a@qY^)}9g=owKQO{(M!;{iLdWVks{DYoGEIJY8+HUVX-dix%4aK?K6?Du{Bx zp&wL6_ZZhCY7O~21!kX=CL-(uG~TyMnRC8Ap@E%gP$$D$6DX)~xe~=oLxBc0hn?`82#0lC&P1W7p35`d&50*=m^(uSdq8?>@?LK8(EfT_d zxJpiGd)(qA@1>#OH29^m4A&wkyS(q#J!I*Ta$P?-I6hmZ+NZc;Bs+U&a-4u1R}0e3 z0?h2<;)^?J&ob^n;S~Jx%f0&+=oR#cXSdOPfpg@{@@T^Se)g91s2F915^riD>HR3NTp3a5*0&`mjhMN9l{|IuM*)UV=GS0E> z+QLZ?3Q(=K3{e1qfs0Wj6TxGUxa9|A4}=28w&J6w&up6rA~HKtnBV=Qp8jU@sU+^e z8%OY^bpD8618c)Ce7Y}r;(1L|%gIatosqfG+^B)q11QQ@Kv5P42Ty|8!?T+hMEijf z_=>X@teY4_6m`l4;Iq#Xx6OJvjW^%cIQC;|TRxiu{f`oazQuqqI{E$15xX`zA1V%NL;Ws1PWEt%@ zO#peSJnDw(|NJ9=BR2w|!7gVrRQHv)h4EjoG?~5N`X{-lCL^S>KGsDzyLh*F*3Qsa z<4TneyvMS}qpZR5CF|jioBF*E@EFC1 z7g`R5P)!hR-Yh#8crFdwT2)Bq;#P@$RShqE-|+m4n655sxM;(PsJeJeQDHQ#=I}Ox zYewJ;Zc2li`zcVw`J$xcXr+~$g)Pdj*Ks1?>5F)nav;Eud|)uTjzJt^FSBUbXDWTp zNdY42!to8|NP1b3z2*1ABNGB|QCgqy*FawfdqmjdFB7TmT@4I7uKr^XO-pJQF-TXY zJ|>}B*K~+Ce7N{+>=tY5GTgR^LtO#>BIRmbS$9mdp5N|d0w+zEt9PV1?81(|2ZE{W zx8v^bn6NDWbiH0sHGR`GG?^V$F5x&3>f@zF7zYw#dk9Sy{~kY+bq&GQGR?U?4fsMM zS9M)oeZ$C1^z_=0VW|Yt+;!czpl}Z3B1x@qklISG&Cl&|LXuj}8xC||B~eR9dsPnY z#vn;ED5t3lgyO&_q(f1&_S>q7*&d=J^})2+Slz4i+x+`>fPZQO6YkCik|R)h5P61X zvtR6k2@@zrku{z_FVoFtzMch&#=MM`;8>0+OP9b7DE=*Tu04v@;WZGi2l^oWS};FspcS4TOe*?!AI0HFx~T{bNd7yQHI^fn1b6qP3eFT@MAwszmbIZTh+; zT}oa6eGuH@YyGFYId_hrfdVvn5^8Lg6pfylKC?J#!xic+>r+)xUKx2gPt>}uytGI8 zTlsZ~I0IE05I%rzh=&Brqk6haf$ilk&rI5t+0|d@9S`}rj3lu`UB!W3)U7WTiB+0` zcYn*6cj&XDd|9>p8}U9#(-X4$z{C%ZKL$NFR@}8eDhI?~f(U0Lv=ODhO;&K&=KN*u z@s|8nlKTi~<_y=VEELjthK&(X$P6TW>~vKsRNoHLCMNoaZj{?`F~O}8%7VRQDSBQa z{N6p28~v)^B)iuGr#Tnmx~V8DqZE0Sku{%V(1~TF1z@18gT#Er)h()5-^+PMTX3sI zyiMorO(JP6>>KC!2gydy{@*ogFjqi|4A2XyI$*%T6BvDu5$>D!z54}UI&K7BFRD#E zJlzFS@rZt)^vT?_?^?-;7p5>>8l*{6y=9W^ree#1PV$eXpQwV0p*6EnmSfQ5-4pt^ z|5z0BBsKj0+_B&zGw=2L=SrNVu}@_jOlxPSlnb`?xhZzgKtX|j9GrxS1ddqk{zx6E?3R(0#@!G{yfr6=o|c7c|q_+A>2 z4m;!k;5U27atK3l=L6_KiUs;a;*;ko$fEB1hq;G3p0PCEeS&Qx@7@D)u9~S$=E_4@ zr1g=!CE0^6$_&KP^Yy8%qmXjkCu|n3v^-qYvR&!!obm7IQ;Ai^=oxca<=~h$($df1 zLq}Mr;M#j??aFTaQ9k1mVsqIkt^Nsi3f5{uFMpryA<0~Jms`Iqx&h91y{=~8BXr;P z`i-UMGO`6OsthUIq|?zWKN#hK;@{{M1{?F?3mS|>?(&3ZK7g4LN@l<}(=nW9i?}W- z$HiW~_--=?(TjSFXJ&BGoM!}EjKNI|u7TBUE{;V3@@RYeh;IiQ=R%z_51>?ba*{KU zNTKrsUiJe2(&Q6R8P8o~qAHuyKF@M-($wa+T4KhRl5Bb>d|&r{nEhQHmU^W@lU2lM6EF+JYNTK%5EY4w5x%ufcPWtYwe%-B4(*Ia5nNQnm({vBb_&e#E(q8H#nh=#%;bc&f8~Z_DSb8q`>pRf0 zvEOtRfu8Qs)_*9fiaAn9X)FZB9^NRhgy#Y3;6#r3?6YrrLYt8>Wj)&A$Sr28tzQ;QZ{A|ofR*;l7^81msQ!e%$4rpG3JD!9 zz`HAI*nBX*KK`2l)|qk4H1H7%3i%7ybXXLlQnR_ z;Z@`m_-vpf+()vwxTMCzu>eG~ex-2XwpWoRc=CR$^SkccpQ=D02O z*x~*>75&kB?fD|sGT}vaS=Vey&y5Zw<{f?6Lb~Zof}_SM-;&P5f#D+plZ{3qRA|`7 zJi2hinvuKv8TJuR@qw)c_>iZgr+8?Jxf5lJQ`}|70Txlv&)9V)IT^s)p!9Ck(o(}| zIjz-V0ob9sxxdU-h>Kg88-om}Jbk1bqpKcoC@mWiKU3q*exZMY&pq5=LXqL}0C zjH9x?)r-V1z(sLQs@@ zn9~dZxq!4asOn_FknL&izz0;Hthd><%f2g~;86K&J4y$iJB#&iM;il{iR|#Rr=S0) zB2&88oX5~ajzZ4T1wFyZa59!}DdkD?nuNcyo&JT7vp?@W4|r;Elpb7& zZ2z!1_F%N;lzWKj=Sx6kEb0r|zLMSxyt)l)CT$N-G2S3HA2wg_^~pn+SbcPD6dt|; zu21hBOX`k!$JfhSDDEKg&ayd_4N~Ar&j+0SXCoJp7P_lP3z&V+QrgWVnh_+tzqHujB+SS}&3wdce#QjDb~)>g zL4$`W4BNs30M~rrL3Lk2a-jZdZXAP**5QWJth-`BAna_!&q)6x9+Eq;29Xx8(<|U;W z>D2&5-|VTh$E|fg96UseYm?6^UpSZHY#z#dYQK6mkC7)`pHM+U$)p8=azvu37X0;RK>^hyjHGx z^6<5C%F#{0j1TcH&?Dtp=2nl7ewIpIS*{0Ww2XGaH*@0?O%fJcuSA&)E)4K4%M%9i z*YK|RsNzEd#t`_N&c&dFBFlE<#fDMws^{!beeI3Q_wLUmSUi!c5!!$O)NxbU^yZgh zDMnkb2|!R6`+c{jzMnuim27{tXBaf_`Vwz_Nfxggus{%_ylNF96=3g z2TCpPi52_`-o~!P;id62^MMvGqOpKBF z6wAj$<2*Anhc}^&f4(|X*mL5u?T*+X3&q&q}IEz?w;64$ks z7B_O8`rE^$Gw?wW_Wx=mJAgA-1rM)GA_*v*ez3Q1r9T})I?N*MjH%75R8;w(C>86n;Tt!-)yDz?Kj?u$~uP-zDF5V zX&rQ=4X*#x>^mJqvPksD*uUY70$CD_CJ&+W6f#6`q&a*tdQ z31_v)j!&^x<#$^@^^}#K2$uTS2&PJEmkTdF7tJ3j^C*X^&lQ@C9&rsSO)5nCUu(**5;E6ttOXh6EU*4eO!Flbye%bUw1D4;(v;` z7#W@o8|ca@K`+dw_ySEUk1m#s?|RaBi;+NIn6}~!m`fjT0RSw~FAvPi#`EV5zH<8N zpZ6Cz0vb@4-r9}UqbILE43BX?Rip9*$NKH~iVgQ&M@>*-538CGC6#A58G z1AL+QI$v}KDIHvxGAnMN=n0n^^W6zh#J>odrp*4b@o2_|)^dfYAdvl8G(VHz2W4Sg zJFi=W+k?@1rciHFSdUVCyjS8X<7=e3S)}i6n8tqs8Ga_m{siDB4s>1^AEF^^xH8Q& zor9@mw5-4${y6XSJE_Yz;=Ph3FMd4x{fyIHk4)M9JHW?@$iFE`Yso-DAwYXy*y|%f zT$}3_srh*wMof2R7epk2xtS z?UOuu)Kxm8>Z0uaPvDuqLOifESM0b;BY4rT(JvUuwO{*abNc$^a*_VH7xna49& zm)*|xLE1LK^0CpIH(qOUW!-cC68-|1c+)@w=mJ$u)BlqfGoYs_okD(c*TT<>|H(Zg z-ajX-zo>=(`G4YQrrCgMgvRy}RKGT{efK|cw1TQ(ApUF&=(D`3=lq9xHJ2{zbBIt6 z(Gf>rc98@uXo!9<4iVaN5-8SN`twNF;!F;f*owZNJ+bRzVu_vXW+*UD(VYnY!L1BI z>U2m6M@a)o{vn51*QRgs<=5j-QPX!aEk7(S?qF91ccDia6<{J^7ePU_qR)2CzihlV z^nCcz9a73zKhLj!o{d+T+W*dask%J0u}TK_FVIEd$Dpe>hX9M+AHr{EY~oem+jR`i z%45)%^d$gqNw`cOnEJy~RFx(G9e~WO{}v+1ZksSTfW_-e@e*WL-W78o58o=o-vv-6 z0k&^A^&gsJ6hKlLqu>0P4(tF0!JBLU$K}!!L#Stm8|+<~lP~dn^=^*9X-wUT*f;!X zYYnBT9;tI}5-{oK4eZyMB6s@nmh;nV?+@=GGI!q%DXOxl9D^<(EKLU2J3<8?R@S}< ze(d6|VDWtB#JpOkcw}2}=$pPXBD08|Yrkd|+e>6pzl}_oc|{B*;Ce-iU+k>F#rjM_ zu{Q2}Ss4{|E;rW`(MWv$p8h+!a$z7Jje|cds{TpIl3Kj2=A9mQVsVdh_qb7Dp55P_ z8D3kDR)}yvwQhzG>VxKL#tDxmdFCR}rj&nv(mto6kS-UAe`Qr(85sX^x(|$+3x)8Y zj*4I1%CcHc)Wm&USLG0rcdU%2HcsZ`qo2Q5U}Amp+V32T`*-f}{RPr|rsJU)-8+O} zUTZ9CIHKUO>Z4Jq#WEAFbtBFwRjak$YS?hIaVJ)&f5XY~u3drj?^2!i1U7eSOP`E9 zt_WHdu@~oO=U;1Az1S9-+pp2{HrDN6BdB5<09n6R#LidKu$1y&4DcUh7vyuj08YS$ z827zWCQp}Z@$==w{0;_@Z%UY2tpscnw&kVBjita+%rUm)d%)G+&n_YN!EJrHY3C?RkJRTrK8g28g&fIe&*bh2KYRj~vy?6KG@6We-9*>MuMU0ibLl!X z%~|fcQ(&ms;Ac{>y5q4BVEyv#-E_r>*|ta>Ff|b)g_d)~4f|j@+3T3HmVFO$zG>GK zm3+VB;3S418h+8Z6dQJ)Dzb6jW{EPnJn$E~l2R1ge|AZ$VHE9oVY4)Se(HBrn1V!@ zp!t%7-}Mt0@XqojjdJMLgNqom>F=~!=}V<4a~qIS6+DZ?o9x!oyKzMxIzL`hm9<^_ z=iMh8%oA+cgLGRMH~y#q^vVt#&C#53ehR3qXqLpCJIcGSkYzQH&CUOq#hOo>JF#yu z3k$y1p}Fg7ltNT53~(p90h}*R!F<@;GokL<_RX|y-^Lz~UU|#xwV)KRC$KMTy{dEW zlG@LugI7=G)3UQGTrc|nJByczyx&J{y?rsHx~$T%*-lyn&ruJ7yjP8Ib6sh!{pf6z zK7aLvvfeI#y1=IV+K*02_u1j+RiD|fn#Hs)h`so@K&ykHn>~tjB|Ickp`G%;e&ARY zY}sVLn_N6J?^dr!Wi9>PM;?y6OR?R$(J3PxVN%sYdw&<-zOsm`3NtemO{-82%D5A@ zaH1pm!rJ^#dASsyLa+Y#mbFU{%e>rrZN}ZxQ&f&7g`qbSKQG~Bzo-mAx(=XrC_9sU zywiC()XQVgNmmQxHFh$4G1%Nq|G_Z5J!D>b-HY*xN1XN28OLhY$`Em@j@!11ZRk_9 zB$DD9gjDA=svr*`UEO-+4L-jLc7+y+LzfQ19tkWfJ&6%I;cI;^ghM30W11FkLBX^@ z3nQX*A=sDHCak4&U4X3hth8ivHEO;3=}8r z#&Ha}?w(t2Zl0g=f{b3p)s!b&Z5Gd~YrPjM2NI%gVh-jFRlOr!DDJntyH4Kra3 zvwYsW@B99a_dVX@_s{PS`KKAr^W67yU*~mR=XG95br{DozcBrz(9AW9Cc$M{S#F(0^y2@c zS;>si(3ekWRrB*vK5@YU#t1w@?LBF|XRNHZ0U{;Kd;f#N>|C zqt^U~bDO0HW@ONWjefEM&6Fp~Es%?HR^%zKJ)sBT)9oI_>q|pOUW6U4GR5zEWm&nE z<@dfYeTh5pAK6EsZYAqfB2fi^?E~Vt^0#V5cj_(SQ-AxVVT ziz9rLuzvKk#M&k%r_fs5Y}TirXGAl74iX6&owBhSaw)>pB!L?2P8Nr5{^PTzJT%O2 zw=P#+JT8>}>qBK8#jRCL?o{C#%zeLOlVaCuV{n=l>$NGfz9SOnNh^tDhr>jS7;+IC zdaHP68<#pc>}c7l7jPvSdD4~fd#<5-5tj|5LQ zbcN+Cv$vc6M+4V8gzD}AnE)1;du3dE63LjVFM9Uz14WCp4jP}dQ49lsK&1;fZ=eG9 zT%LQ>0a*BTfQ!!`MefX16yv=m{FrPKd8T(E8@NB$5JB-YPaHPF4gs=_4pVo7(jpf# z+ZAjj0_xiE>2v5P#HxLH@~ndEWYS(IN7eg9``ZgN|IsL=D?iCAKW?X8`lczt-oIS% z-PxtMKBL9Gk^qB>PJ;%%Dd!b#GDpoyWM9^j^-cG_d9fyu71QxU>4sEwWqV|(pWhXA ztU7%?J(UnD#0hHP$&%DRENiZX~F$d=f2QJN!fD zkHbZDBbhOanL;cR2QJtJQ+(YVwYUOAiuFA8rixU>;8y|`-ms z%V=1;<`}!XqqC+xi`!%j`FhXhbKzE}%cOI0+$~w@{T%%zgCl`k;+|cO*ie9oyHK0?I;$VXAUU!35 z*}?l;8s zm?=7}DRHvdN+4S$65b~cDF&~u=q*EUf}6j{<7E1IC`b|x;Ikw$MUBkwL(-c1XRsHZ zwK46V|Dv9NWK)hqGt&9|rJpNke2HuwosqdcZ#N}@DIBCKkkyy@_s$c;`^`pocV$#$ z<(Ac34BiP8){p6Sj_N%9>iFdR%{S;xX)3)axT-3g9(Hq)NxC~sdfa!9k^H3Q)zkK| zN=whBNihNbI1wZM7yQ|m_VXoaJ~P_7U}6OAEZfOXj2x{T(e z)FKx@Kp2oulA?Wx5zCMmSr4bbQ#-yP0idJ9vj0x_!lfKZnikIlZ?zkV?kMxB;{?W= z_r^J;6ui@>WG%6e%@-l$i~WQjL~!3{Wx?GZ1xoi%?%P?pHha4npUXP4>**_dNv8NA zv(i31GYYmW$jpVB~GKozyDDW%`!Zwj#Q)gg*gD~9C z9p4G$L9^RQ0F|j7XiYYRDTMw1{);=6sk`wmX0bx(w7K6S2~?TX=@&LY zmOT98rqLmBHkS zl#w$Lu)h0M%Iu0as+&{G%db98?8DSbQH?>J2vc)&awnY>N;bs#PGI2c{EE-GGK3_s^9Q~{w|$`g^Q2xjsBI61gUby>yO?@iJ6EHpS{nF`3t zxX|_Jr)YPtS%WhtZ9U0WBHaw%NZHp`keq$Zyc!aOIy%op%=}O~R1&E7(4do6b-d+ucv?q4 z9fOR#*t1v30x7WZ{+%k)Bce26kSJXkxZe@ndZRw3Oqb#EQ$ig57ls)dVX=NicG@ch z;dH6K(!qzn3B-{b-xW8z0PFQ} z1NnjkHHZ}61WiB+Xh@Q-la+swp8{t9W86Zta^SZHwT4s=odZ)r+uyj)A8OcX&Lm5- zxJrb1zF9^ra8cfu=`R@Q861a{*la4d$xYrK^W52~sMy)TI#}d~pYQSN0$ez#7Sht3 z>L}wR4w0CUo#s?Pp>*m(7n_1v$f763ZJ^P`Fd);2a{@9~qdP+TJbw5lF`Fpb*Y%Je# zLMEAS3gqUp7(j-|KLGKFkdAM_u9fJOQ)iL9d!kY5fxE4K72i_yjzpP08)+v!eQ!Pm z)JV?cLJ!Ha1UybK z=`CjknHGLM4T}8s3qHxy50}3gG&dtawVB)mdV2T`z{s5Cz=ao-DI=M8OgpY$M%LCT zMQps#I$?ulsmvGyc@>O)^DVecmMx?M>P#(?sMpRIsAs&dvG--8-k<6;*Fv}z_x{<+ zwaqlx&D=kn?11+)kNX+VY&Dpf;<3!mBJ~5Ca?8>#LBeU>1+5xvjB5PpfiU5u^6>F!bi+ zLco#Wc$hC(gdbL`R>tIu{5yZ8YPP~;$0|OGzq}M~W6)nRbCHp`C$A5qQ-+VAGJm5K z$*hl2c@URsn)l^J^Wa==$z@(Nzb7VMC+(?{+8aA)E4PNTTT}jOh(@P-%!eoG1#Hwo zx&F6>r(^H`U8`3`8A5U+@BghwiGs}+9*OL{MZO3k^cs=gYX!!woSA9P&Tq)A+wh{n z>ifiKm$Kv~)>BQZ!msVV^>Siz(K7{925x*eVSgO%N!D5pNn#;tF8#i}l)$cAH#VVx zt9i25cvI0)Mk%%QwYs$XgRr}wnq%a}(;y3e z8LY-f_Qv;)<*D?WxPC3-7|9nQiYtyGXX2=s=J^vcxz23xA$56BmJGOOf@GxqyZ2$WMJ7}>pd1}2<3 zW;)49#_F)>HPky!x_HG|p#fy>1!J z?#6Xu^lEA5I{c7fAd!8Z=U0tdQ^z8+zA$5&<&EkP$i?`T_cLtfN9?PH8>xoJZeyO5 z`RKn|q$ddG2!b=ERPz$~W^H?>NevGEMqW8Cr0K0pCfjW#5%pIc_8*)DoUftL!0`Zn ze9sI-Rr~ege`H+yRbXhQU`W!Y{-9pRul0D}mXEL7o_p5QFTv)eJ_u03%P58eP6Cq5 z55n-3d!xu5n_r;a&uZc?Q3Yx?O!+wAR*K=S2{^3B#84nVbvXNwHzGM6-a7mL{0Jmr z0pJmX^eqr*K(rkV0;bPIQ3x44cp%X+?5Cz$PX2_|(vg#q{K(&@0Xo-TMduO5dG#~v)I2MggPk{G-!u$ zQK$Hd{=_Kr@-K}f%JU4}q@p~;s(wAYE?y~H{h{SznuubRSjN2j{-YIusCr3yWX1M0 z61Oe@TuCyfiBkujR+n?&_CM!%tTl&P@>YFd0Of(u&rWDO+s-hf8drVo}k zjJZv4`kT@?LuMw= z$b5qFKBVYHWoWNJ-Uuz7RrXIG>7upoen&p8zsY+s)kge(2=As!9|e;tzaRgdq7WvV zj*Ec;k{9ZVH8u&cpt5dn%Xrq-PO2vN8F}?2UDm$YDav^uzq1V(^Pgl}{BD7#7yhCx zZttaUc~stc3BFH7iCbKXmxOX!$0Ep*MZwe@aFLI#h62liaH`7;$mKx96A7<_N6$V` z?b}~daxsfbn3oWK75-4DP4i@!pqttm;Tzb}90`X_FNPbX|IyT4jyE4$&-%lworW}h zupj%#eYHsb>v3z1;XrjpW*VH_28MG zxTW#uYh`uBdU;M5o0+MXqIb?*n-G!Xl zI`>Ouo4!?jMJ(x=rs$}4zkdg#)Nr9k?m0SUwU<{bvs8|q@sm`hMj{@!{pcN0b)_51 zraVhstJpKY-}00?YtC2&vO|BTlgZuwII?pg;wElxz6R}<9Trz;aVcZ#gt{h+#r{HG zY)LNF{M3Gl2zD_YJ9Pv@SH~a6#JtjjVnsg{SKG+Am!Z|~pn(-yqW*GQq}CnHN~63u zRdy7AH-E9^^Nu~rml**#WQV_PF zZ;CvI9`+l?ia{-OMt{)1%&QO}&y4WGxUj?o$ICAF;ugBwuI)=NY|)2$b3;2htNfhy zqEa+J;9-XP;OdM5pTRs~5!3j(g;$p*KeBs{*tQ!fCC*@k4tF55(8U$_O9;naehr?R zEDmGYE$mQos~1@w@xrdp)W)9+9Qyd`SIP{w>iCFPJAHy(VIMSqihq|MK=Hr&O1$Ui zfj6=^e;oFBndcT`Vy4g z>N~?g1yDPAc1v7*_@yapt)cPuk0{;FPIX&b7O7j*R`)+>;$=`32E|q~2WqJ&SiXlz z;_jjCb!!r&hwF?^$tL3*qCq$+{{8cqZqXMnc%vkhzfse)ga9z{ z7bjzd>|J2eHPDkw)v5d(^B;|&%&JGmW{>=p6Z1cy^9}i$Dn-1$%r&ZEyM$s5G&hS+ zCRp|5$|iKJM7&!weVSC{fHoNpYmcX7Ll-MxwazHipfVfyQ7YWqh<`&HgqBsZT>OXyO9{p7BeoRhLi0sDi0Gy z9c2$k@xEj?l(hbAE>=$-q`Q=JTIpVB?;ui}!_=1Fss=BS;QASuW*tq3lCSu9`kV>B zTPfnlm>7S}(NQ@3n(*W>z=3AanT$y)J)XouZ0R485$!S2p`{u3@@Paw_h=dEsanm(&GO?1IVe3(i*(N!IWE{?-@LLj4MI z1XRLYOcTsan}1OdwrxBQIIV;>~@B-#lcq3|sYT;7u;49vvm?chbZXKVkr@L{yn11pI(H zC;d({Ax2iX-7rMOv`f34^R?V9NOV!2F~QB`n_cHGRAYBe7!nJ6Ij&gO{=GWCy%Fz#4S7}q(T|{ZeM&OTN;lBEo;lHH`dsHSBO3U

bAeq$d1AOD_Yo{sEMhEzi+=K)pBaRMH$Ai5^(9NF;)(ghjO&WoRn zl8gKkO}6~hS!yvvwzK)nBh9kpH(+L0I;gd3Zf^azK)q%WNsNjh-2RUypTP#C2`G@| ztkfODz`4o;;QMt=fQ>sf5U#k=ktMt6kdM~HPeUd-53LjKg)FG#r_SnXrRO|9lQhOV zQis&%B$|WIMI#6962Zrw%sGJ8h^b$LQoTA`VG00GrRZ&-$^3w2JwqT`gRMYM9KrMJ z5{o|0y9WcaP3w>uj)q^()e86a-`d~5U({~bFM_cE4JCXmQ!LcivOfROTnGhMnmE}B z!MV_p-$(aBNL(PJJor4h7wXDWc2Rnk2%%p3KmiHQhV~>BSNsb~#0CfK0Qh~zj)Fqk zv&26j?k|l*`rVZ5sVCG}iuat%%}t*#qPaFD7T@~vdY?|HsSZp^Vh5bKPyQDc`#=5d zNE|V!pF}{)Eaq+X9h`%ll`qcr%cm0X)tt^?&ufXPc{pLz5DvK0!;}4dYT?#Bne)Qc zEt7@gPw}>OBx6DuLymYhewtTE|64f?{DBr z>hqR)OlcVT;igvPy`<`mi6UdRhk6;ZS5&9{Jwr-7c1P<+4Q+3Zod`s;E?s{V7vOFn zS3lRDK|(FJCsWcjrCm^*?L0ww+*KjQj2lY1{P$8S&0uxbx876@G^no(uB^VL6YN)6 zf%4V4CoxhdAAZNPDPv8BSM&hW8j0j=gh}yt982NvcWVBA{6Wvj;&&uX86@))B^&9E zI%`9W=msZ+?+C+|9d~=9`;0ro;{+j|kF~Bpl!sN*GqgOfNY^DkUQ0SdQtO4hZf5CY zxnSpdV?Ea~h;zK9qk0G~P;I>n2~*ti|8ng~=wtKz3&`mvTWFbS{2#+lI~8M)K)XliS6a-Po+^1zcJv)PKIjHeCj$M>N^7)Jw4H1ur#sD|9U8va*v?l9`!Sd3_ziFZ9UMkj?RL`Q+MGK*y`i0z*s4cBsi#|LwGH zf_Fb}j$w+^s*(BxM)EMbTeKC(ZH0DOJP7y!4Comi2`xNUuiCvTZdJ3DpG;gfRtv=QM;M?eR zC)p?=OJrz#cM-r0)o}(9`2v#55@mFIUgFP0HU zx+<1!9|F?$-k;m!!XbBSbDy(Ld#I)~Wv@TJOK3gEAAcWDM;VQJU|M)NuL;jWh$RmA zeI{!~_3PmpUe_)#Y+k*SIhp;MZs3=r1tg}(>*D1Fk;+xtOShjUP9OJM;#&~z_}AHj ziJ&WhUbQK}OUCLbmnkoSiIBf~Eb>r8p)jS-;BF~nZzcV-gLWAF{W7E3>9_gDr;HvV zS=zYwEE}h6n=~7aY)8#8QgH{0C#I|K%LY{16I^{!hTI13pIU5dD)7{jGQ>UHYM5|H zNL*2UE9y>bJWEDjU`&dviDF?{QR`p=y_uQI`ZT|9ij5Z`wLL}J&hq>E^w($a+5M%@ z>Ff(A9lx?nNeg-^qL}P_v1`cB7XAj~4i!Qu{;Z3GyqV_=mtJ5yuNr8zZJ5E}X$pBR zS=SgRaeYO|s<}z>(s^l-YLpYsxr^nsRc3g3T_fW0v}{!V3)w@4kOU)oyUB-lG8T)c za}gVImnhrM6YmW3zES_(a8`!H=fd9BVJNY1wRl%weM()E&|1H#|wLuw&_#Z#>s*ddg((h(Z zd#?K((-|+ZL}r^OYN+G-Nc!zbcDTLQ8kZ_ItSWYyFe#$eovev>QR|8KdA-XO6-L~0 zIwBUidr+tPml+bmR%}vv(~R|>qlFeM5`yklkXxW~P6<6STanh@(H@~YHaPj-3cVgG z&~?MQVbM2aORAeK_MU`PQPL<`nt#Qu-wf{M-K|PD`4$hli0@S=6?Q&~EjG%ND0kB- z>(t%PNR1!WmqE7<+m8 zhE%NAi-t@Q#SZwKpTd2i2q|uK1)>HYp*yZAjL=Rq!%M_Tc0(@ChL)0G(GQwjeAT~x zOjLJyW_h*u8_JXI9&7R-KaKJ`s8QH&3}lg#7RI|F#EVNWwcJtzy^>EG;uU{e-72N0 zSv~#DA;tj7k#!uqNMw11e16jn#%HxPuaOF8-3&H*ZN{X^FxR8W_< z%FfJn=R#5JpQJa^jt3h{)i=hi(s?@72H!t_>jww-37?zOFH=33k&hC2RFe4GH^1VxG`hPLR3Sw%km{0=W6l$_p3`^9Hs9Kr z32yWAJPOc+aavDLq#OE$`cd0~m;x(L(hh_ZOo(jD$Twj!%_O;qC3lIIM$x@*Qw#!^ z)d$4lQ$Ftw-!S*HqXW3+G9?<+Q5FG)t&Xa~jlx+Cmx9oho_n5t9QEANj6d4fSh&{a zqmyqriVFXUhv=%d7d=_gwRoe)ie;#Do`S(TsFs>H$xnV^T%ZY5dHh6qnNZORlhdAM zQv$P@WS-O8kKn6Y>XV*Gr=rK>0901)UnU1Uug6@}d5QriZ`FzV1?vvCPU!dcXF51u8tPx=P8isjfSKG7NKZI=F@E&*vU&V$gRE z?6hDb++@(u>J17{wP>u*C-My(rEAUwdfsHcZ_AfdvsI%QTB7&)&20{w1HT*r0n~;+ z24GCrB%;crKB23`wq+(CP>$x*fO-{yDe9A(EN^6;W49K*`+FydX=`0M^9n=XlJ;u8 zL^*frK;U9*$I~h_TLMZt_Q==2k#4+y%*sB-0q4bWEPV{zHAM$K>wp*%_f83@i7J0Pdx#o;0ot11ZTWJRJHCw z)nsCo7J;ox-VI`U{+!-ou9=|yjNuPYXQ#GoE*8Gl(V&ovfhHZI&uP%SU#Gp}%$km7 zwNTF*zn#6uUY_G_AQ@6&4ygWDpjyIJOwb+2ctVJhnek-lr5Uq!yl(c>N#77t!%>qZbJ;9n2dfNACk=Cozh*)8*PyS6N5 zEY;KYG_Ibj+I$jExL;g)yQ1#%J(P{4jqLn|;%OHr>i#jnL0@Y~;|T6wzj>Afp-W-b ztDzhBUx}D)59v|@J9*I_06Rw9shv_mD?*aYe{RlTenBI~*ZYg#$)EnS;23&|bQ#&% z0I6BnS*e)W))cOr(7f!<^-V}&Su%d}=hY3JLbuN=l&ttTtm&0UvnPz*HJT6=*vEKNU;gaB;dCS@EB3be|)i*sqc@V#= z`es>I_=5%TxOf=O#UzG10DIo@6n{98`%IDp<%8ET-{*;hx|=fSB*m?_*Q>k<-YrKa zY1Qr7g%b<7uM|&84svy_mG&Rq4S+%6&q-j7)Csbq#T-yz;b*DEb3O%ocs&>MiaUUZ-xved@EnyGu?Uq8LqYs_~WeqpO!1kcsr{iP>Xtfdu` z`_=Tp^#K2CG~R|`6M?i;CIMV!PKk72*^2WUl^l(IuJonfz5{nxh^u*vpb>(_Yjb2C zRRv_=pr3q)GY)-;)C!|_`mwJV`JIA%Vum;~@U=1jG_w@bOZ=7qw zE8cwt6ub44BSJeRA5_<-ZWEs1Cwn~g^SP*OKZ`Mm*ZEzV-pdI`d|ubjzSe)!#=OuR zbjo(>AnlnzIg*keT&WMPF7pZYPpkz4%bnQxsW;sW!A$HX4TY~{Z_xD+Lg%HG`} zSAK$SPlflatQ8hERg9`l$%|7|k&-udo4`@wzi)!cs)X0k&#x~#^=&@$m-*(^XTXl+ zJCy&eMkr{c#6jIbb2h@Lp)O<%S6L|DT(3KQ#8q9;>BE=z?T5kTGdOzQugjSG6=3uJ z^mIhm58-DF3_MV|8WKB1>|aVbi}CWxeDNr9@u%%Acdxj;#0=zAndI;Ul>>hBhtNxv ziE*)jfxckXtHEuBLeV?pjV&T2_Z~Z2;axv9Io6l~LDN^V0x{(y=)n%iC)RWa;I^{q z-jv(_@+Ez4$M&#)_Vp0bqf36F_4S%aJBT9Tfb;4U--W_4kz8S_IF&*;{!ypucfq!H zZ4|4;YK!Hr20dGx#vPo&4OBaCSU)8l>O{FnxI@VUCZsdnX|Oq$dG>0q7sesNsmtD$ zxvSnZyw3g}G&I9ta;JTBVtLuggRWXiQ(7VZ9|Bi$yxar@NQ+z=(h*6?7C;d9oGq`96n{ zhupnhe~vXXmqdqjEiuq3;{zKr#`c55IGOnGT%wT=U4b*!p<*?vIKo7KI&uYne0jBH z!BsyzG5pyJb$g6vI|%um1zrYat$7!TGh(Vtxfv&zsGb_V%`Au9+sK8we$4YoD_OU& zrihjDGETiTo+kb|yC<0tiJ0|cO+*UrZK12gd}1Aq5X zIlB&$W3k7@D`@OPJe)9#Z;Q{nh7X8Egp?azjyA5z&`oF8yQFPBqkCFZp`*uzUbF5b zeUXKz{2Y!w{|s;5x1sa-f>Xn2t16-&+EXTz$z$X1z&r8*{R8^joqB!~r$(U7mNn?o z|EuGX3ChUgF9lmv zTr|P~R-OhtfGcP~R_(p!i%uZ>IRSaWMMUWH7}=at>n?nO>J#Hh(~g$Jm3<=cYcIEx!|ht}O!bRKNWaJ->7B z!a2oNtn4S7aJ?}}NLB?4sJ?U-YRctUymp82YFzg$-g|VV>ZenleM{70{^~8}*~QeL zUcVd3Zv8v&DldQCUizc$mnj1hAdm4DNaHV4TTw$pejsg#$t|dhd8!Rq@t5a}(VT9L zBON2_-Be}+V!kV$pL!u{*+5-2Q3scpRP2)4?0im(t?`9%X-;afpz2B$I=t-YHUY5p z5iSTV(nHV{t4K^(Ld9Ac<+!dGY8_rLSMgLe&loiCJFsN?>N}U(WGZ#FbwzTJd>x>; zOxID_K;-dwrha5D*t@nHVgy@c(^*MrHvIMVz!|N0B zizYzdXSpcpe#ld9?6-@z6~2H0MS6?;C-AMW!K+yaF zeG0tyyD^Liwa^kbtLNboDQNlFqolNAta1U$^D5E`Y0|l%6P0RAZks(aQ3gRO&qz ztD?RHx>6)j641fOnV~MWO(=Wo758#|xA^)SZ|{2^3M%K}hTl2zc3$?5zcpI{%fj$r z7DuHm>#i3Qk@(c*iSJ3f<*m0r#uRB$$9vd?;azN0L7`Davy_u`X2n4t8Ts*ji@y!+ z31vryZWx9Dr-t4UqPle#j;QL0gLH)kuk*5)O@RZ#XTh>V>kd!?aXEVP zTtx*OOld1dan8@m8JLv1|Jn=*@19C^=|UlMHmBQP;(`8Q9A^igI@#|Im*1P zkwqp=9`b`%5y(v5-Y4?`|5(2TYUb=@7YcvgoMIzSwLnqu=wGPcaDsI-L7MVbgB#Z_ z=w{3&`PyTCcFWkE@W_8eIZY|Z8ueuE(y0Jd8iBmp(qD#_LgxHlT(G3ht_=2K<+qgw zsiWWh44R0&fN<9f>%Z~m71XUwZAH8&eo}}NKOWhR3h_@#&9F+S4${8PqO&i2H}+ye z-*r9Q{;^M?+oaXGGWSpiFO^*E+mf5DxF&t!l)H|8PY1sUv+4=QQu{R}$+Eb;J`}K6 z;l?|~6@wyuK$lJ;@oG%CE_cz4J?qZZ(fFo z$YlIZpwBn7AD+z%zls&;)GHWVJ&CD<%n4D2 zyG>>OLabSor?KADyo!^LM0k}>b+1W$kt-Z?Y7&02|53U?t+JCxGa@w%NvXmtmP2c~ z$(+SK&9H7FmO51Eb`-;z6wxXRzieydIh8alo^23iLi_Yh!oA(|h0j9&;7C*^L0n!| zsWk9{G=Ec9CcB_)WgMM9CN7#tJD}1*hgN5KxH+;Rzw;EV;5VWY>C9$4hM5d;cP=;N zjs0WW2CsK(kP0rzJ-%~|_iopv4v@zP{IKeIuN&I^?nh$#&WqUG&s;NRy?-vbsVpmS z5)BNwsgb^qCbK0`4MXg}m48|RiWiyf4fHfE^)564vvVpGBr1Q+hcP%^=&=emgxZ&r zL=me&$9;pY7Iz^hn^E~#aCWVlTazDBj4MISg><{;!oTtEKniuC44ka`KY+&plu0;t zg5Ne^S^kZ^!^e%k(B0)*{y8nlg^FF}=V_jN?f5KA_w3}~^jA;mML_+90{fK1{wZG= z)8_tG!04;S!mE9w*J+!ki)ouW_FzLOtPjb^eW9Kk%d`xCqbZVcJD_q-X=WyiK?3D* zEox5br^9^~pVS~RRO_N9ccN#kpBKZ?W2_(fI~HPu-rYjz@p~y(w<;D#Bil+sg2Y=6 zH`=LDa4bFPZr}`|n3xo_JR%erPU^WaJ|(QLzO4Ri_|AjAt0}sQFW%6s<`t%O|I|V1 zniO4l@OTrn-s!t!euSb*O>^~|)A>Dj=PQ8b$ch(bi0sq=a#|QG2s9`=rD&4e)`}hO zW=~6F#QCC+>I2`#lm~sZh))1s82{T0^R75Y;m-sK>-jI!wtqPwni06H# zJJBemUmQvjf5~f)cYe)zTnu@UAY;cD6vd}nb~XTBn{l*n@c1aizhR@5q1W_9f6(v%EBLt3y(^#V{uA*2VOp2joso!}tEbj;oEqh4)zAt0g!bN_%d+4YO%7j{!%~T8}nYJFU7C+BERo! z!nha>9ZZ+8jNCdoio)GP&BT%j9F&UMn_&he++8L>*q*K9&<1EtHuyAcVYvf z(sH-_(Ge@pSqrk3*52gvo5wc0W36}E_i8o{ev=ZJmz$gXMXm*pw(+VfQ@@{7&bv2c zQLa8TOw`F!@pW{3nhxB+i_Q0gDCaqe3?v|M>g>kUs_nRgw~vEZx8&jnjP8dzsQh?8 zN0USZ67Ss_r!_tkJ zsv_Sf>Mzfze-PNT`i?Cf5I$6G2!XX|8hAT3Oa3vt1_9uucr zBI5_+4JkC2N;cF!sGS;_Y>Kp5sK zYOScLGJm^d%Gx*HA`Z>m_KUCY$Kgu!u*$SJ^;> zZ~w2J(^}z*Xo26PK8ynttPPr41;}|#ev|1;O*;fUQmFL;QxyA_Fc1;QPXDPAl_6r@s2@fW>S+L&$ zqJOs)#yeVBHv(kiy+=Zq{<0Nn^5Hd7bb{p0`F94!rYK$Pur#?!v##yVLS>Tq=g+;Q zOdMG;PP|c6rji*U3;56`G~SnF(^5@kAcq*jw%?s893vu@#;$fF`5j4 z`zAO{T*Z#tNvD$MLyRtJh34EqF79uKNsQGf3VHU}LbYRP;-q(F3>COjHd@GMaQ4JSiao}J#B3kll|u)M`9iB%MefmN%Jy{SEry61HyHtM)012H%M zI2E`qJg}^kO((>WbO&v$(>BFqw)1)-eqEq;XbOomPi|QvUENEtZu=6 zvwCYy4cRKGY7o;W?pEHTJzuBMT^OQ_}J zc`rU5Y@4Q$s6@eMFvZR{)g+ z0SyJ8a@Q9Fg1K!6^)BBvl<2#j#BhELur23drKh7y8G~K{emOP3SdW7o6txYW12cmA zNw)23c6vr#p)TMdT;hfq`0UE6%Xhy*zUkOtUzjFco(0tQYBP7c zK;?_2P>?sGTI1_d%ERG9rVadoea!{O6_N=R)mFqY!HG1W2+oS$HN76$CWV{@7A*f&H6V&o7$`Vh1syJhm zBwOAmp>Jv9Y1Q(R%H$d=H+J;bD1CBI zhN7`%$ihf`cVCl;g62$*U6ZL$Bbe3dOKGvIWt0S->!Pb>B2a;=v zA7#T6o?kos*vLY}hDBKXjk+B^`3T^tRNZwQW;?#tSatDkD%>KndPv4V_!%SLwG^L- zCU31hO};s61OlJ%YhGkDe-z<~BdniC)+x*COi%yiEwKC{R^Xb-%NqV;@I3#kUB1?# z#gUFp4M*HizZEs6B(RL+htMk3av`(}ql`VDX$4~iQm@W)+3x==~*8EWnp1YeV zU|H?;0fnwtQwWFYwxG&xc?S777h9^dW*fG>RvHl>A5y|9DkzW(r6Qe%dDcRmdjU1= zj*kx)E+wU|)y6JEp&^Jb?<(Z(-X9^riP1g6#H z7F!eE5)s9A3(~~h6Zfxm{7gC(SX&{BWy+YA7j36h^IMapYSS+_rFAK95%)FJSD4^7 ztz)$dT-$uF%>g0SB`sPW^T}%FW9O$CV-e9Au4kRTf(o6(baK}BOiVH^pfqZaR?#k> z_hEXR6?*Do1tD{GS0pP%^exRZr6zUeTKw97HiXuVw*)=MkZ!wZ$`H*R7SYiYC&q%p~S41#bld{mBOG)KgcLnYf(e-o6;0 z&&>v6-|omf3z3KtF0xQCgXLU!QHYWk)Gb2wJt zO1dS#*;BID&=t=n|J*l+38If%^3ms-QL$0PUHw1z29joMk#ZS~T# z$2E0tu5Hb^5BvcKb+qLck^?xC*Gw2k9t`nL-m7W2)gUC?Dz4C|HkV*8FDZ6Dlj#!U zM%|Mmxb#AL)d26)iYV$y1T`mtOOGzRqmT%^t{)a*ba}qq~EP)*G}S=gd!?| zD{5t2F%e~!WHX8N%ut_MH7xUygkD_?VN9m)^ZFimY|gAPB2Y!FpA~nV6CY5m_}OrI z!Mg9ewX2)IeT{fz{>~LkeOvbsbl1oP@airE9yEvDtKqRy>N%fxJPWg3MWc8gx2CH9 zYu?tEd`Yu+i~XUUAC5$5CmP`0d%ayWIf?nWy`JgS`=$q2$)1dW1{b{t_KCVLbXlH{ zZ`A%TPq+W~_d`3o+r?G}US2XUtUIFg9aF*!-Od@X2huQm2b*VxeS_8?buG+jGI_%z zsWJ`tNOZIu*W`od+S*2Iiz1KYIOUXbZ`E@z;(wnt)-}0x)9Qc1`bxVdA`FMzX~X51 z;U|;)8a33HZn6xJ{0o##?q*Oues&6(sW_<@rgOgxdfqKj+s;Kc{t>2vxc(DiyA-N> z)4ID|v?l4n7~DZur#W1QF_D(0gH@}O=Epe-hpAzJZ56IPCa7d9Afwqor});9c|d?k z`Bm#Xv6ia=_M8tNvs)z7oXMtX(6I;8bfD2;B1BWaJ(lfhxwiZP%=3a`n2w*z3_VNC z!&pQiYtUjucJ$I@FPwq=B^)pt}hS?PloA&#QG`6hNsk>lb^eerjfY1s8? zPT%F&dyV2EtKnCc+Ec*i=(f4SgmJ@dbkn{R?eRDrAEA4z&rV;}I>UPYpV90CIz8Z5 zc1nk{C+x>UEj%3K2#L&`S0zTGt%>ekCmty}K1gMgdnwA5qVb#wmQDV&ujXH>2TMbi z{v;hBW2rR%Am8@Jp)74rb7*_nCp(eqin&^RdDUI;AQctgvz{LjHtYSLh9qG32k%{& zmIMdk4#gSnwK5_Ys18PCXR#Wfx6P*AjN@r*zisjJ3f|PKpz(if27MKFiHKZIingE7 zP^xsHii|pmJG70-JG3?=Cw|c9mGVmYaZ~}G4_cg(4}>SkNJw>N99ao`zYwu&Vq_y3EuKY@nwjsJ&nvM&*ZtfQ=1BTHqO zBwLbF2r*>|$(C&}W-K9l_M!+O>tq=_V^`MfjCF?W!weZyGv@Q${ht5t{Ql>E{^xwp zbDrZ2hvPnT&2?Y*b-kC@aysw%b`;BV`>Vq6a3|9Pld-#WdtZ?%^{2JE!prdCG?+7l zR{?QWqSo`yuHTcz9>E6gx>24sN2}W*S8rcDXQ;>AVjc+=Zj%RiU<%_@&(%+kqM_Y? ze|suMQy;duzb?lMpQ|qK;Yz&0P)rv9M7~t_(>k~yd=zMUQO~cUg`l-#w~651(7s8k z=5xii3EeU+GZDG??}_F2(hZwm7j1~vC4dOPRF2`V0B!wH+IdA`8vnCe+BgsmAcny& z4+DcgY&QmFAMK6e^EEV%{`WloTc!W!#Uk-P%0T|M{-4QqeH);|M_BOglKIbnDszYO zRWw;z=`4WP4lBU@W9@-}yrioefCZJ3-GP$s+^mAY@sAvjH6pUfr@C2mYCn+yRLR-uV)yFg$Q=KPnxVTSFVd^t6US$g`4%2qcmK%HSRut@MZomR(a!hy zg0dz0%I?$er@d@UyF|6jps63D5R$I-Yv!uGw}s`>O~*~hJ5mDRr~#e!ECyT^^S!e;15;7r7;Olzz0SI4^#nU0H5d-PGke!aCy zM*1;~uIZ2V28I#m)vQU+EB;v`aA@m$ukV1)!sx^J@f0O(9e26?;~vA{^`iqz|NOaG zEOS)A<{aSj=IyF6(cvs6Sex`MB(XAh08}hCd?RHI-rpZ~e_t!q&7~>l5lSMCqc#5) zi*)msp7NS~h?1<(IJ%{@&*JO@VurCD$fm~7A?NPe+$r~Z9q_G~h27jJRvs$|or z4p;4uTzs!@mq*1P|CKAc5bgJ82*~I^BLJmBw*w4q1$#d#Y!hmZ6{Ccr|IvhM#6j*d zqOeRhRz{bn=E*|ntGg7R!s70~Deq|#+XOwSscjLr2~!ECI}e-R=g~1VtDiQE8`W_+ z0bE)snLYpfZ>4*Jz{8BC{;Ul7)t*91ZkrR%f7q<+znXf|*6{qC&WseY@M~ecr2HY6 z##I6=Rk$rsEu=4@hW>o@5N5KcK&-)=je}hNh~w?scf-S zz%%gMzw*GKismzi5j@AI42Z5Wz#rF*jHGCJtS}r8&7?b!U*pTR3XO)$zKqF?CM>>H z5{XLhUhFN3?mT^2_|eJLXM>$84@^W4di@IwSZd$AY5>>uSGy+`@n!NAya=A()+DTf zQJwsI>bd`a6_BmMYPrE~FDziScJBX6L-+s0TmMH__ka2u{aO6C_S&5Dk~M$6|LD-b zY^Nrc?HM`E&2EhKgX!m}N@Of9iEFbLGmwC)vXE>eb0eHNF}bBHCft7;ni@@Pb*cO;plH~@d7qsT_uF!JRTIYR zmy>KN?BzS~cQNxGev?Cf0&mhi?R9fQ{Lzc!MDSi2(RP+}V@4gx@k40WL132mz;>d| zN=CkPS9M|D(Bb+o`Y9EtE2nCo@Jnpd*iVcE*_%W<>PaKsBN2T60I42d$Re{dk$)S5JO4ebnGZP45#Qg)Ro6V_Qf74)YxzP+S{+#GBrc$OodrY zqNidE9>u6gcLsh;ebRC9w5rXvZn=t20d(kCdYw)to7sR*@ty8yAK4i{hmt3ue2l>d27^u>$Ugth98No?pY z`c;-ZO5ARKUdrNDruX%SjlUh`!T5IA~rm&nwIjP}yp z%H#LuE&Ne-d)K#NZ{cE4x1?Sas?kd(O(U|Tp&m_^RgRgEFGGzm^ycX1yQ;*5S+N#P z)swk=7}M

hJtDj@MgVjY|AxNw`pc*S{t;;R%OpoGH3H%MV5C9lBp_ zGBt8%1bg@7U))*YLVe$__q?q>wYQ&Rn0|jsBE_`kLzm>zidnAB7eTDm6}G4JN~;FY z|LAt5Eu;D-vUoE}$|xP<%`ej)!YT|DHSP{>ql8}uB3|GsEOM%(#5{v7Y&`;kCLujY z{UxvmGC5Ce_BSYgg)3lA0o#&qQ4`xu%CRi&c_~z!`fhYTocTya$gr~KWrd(XHGfCI zq_?7WFz(5#J3%3t>qNPR56%udYY$-SMp}1F_;)W|kGME=Ehace!&^mnoT%Jw{x(mN z&=9)@p(9sxn-RJ8m*sWf$BJpDqPCo8-n&|>ypLdE{R&dJTZSpxqR9Jsx?FfAG3-_j zLt)xDeYCh=?w55x`MAI**A!nHZ(=hjclW{TjKcIs9tWKk5cG+&n^WX;?CB4 z8pNxaXR5B;0z#zJESLJL7opg1yG!=Rp1_wkO;~?}_~gUv*PW>)nwT+==l$mLL8c&9 z+rZ55%hXWdP;vsGOFalq&*QWDkS2|~#qAkEidC7*g{`Bmb0V#L4+6QK%8PT_xl`EN z1ZeD60P#=4Sf_lLDamg_jr$yw)?*#%Uf=$_(9ZMrfOFxKBgs^{g~U6Kcf2#Ot>?H0 zk>tzSF^K2rM9>8||8}D;JCVC5A7hH85=Nw|8!PNl=s0ydD9zo;M$#>+vfSl~#_gRQ zI$kEpzeqgL<74_pZYC-NB=T57_UcMv2R#+oI=X+z)U?miq<4th|IsaE|1?=2<5avP zpDG(*`B?jo{rZE!B9Q)R9Qvg>S$30p0lB);4@!VA!vqN6dt}Qpo0lPZ#V)~|jUD11 zrf>HxE|;mLnPeqi&StOqk_OwqWjk_Oj95*F@PS|ClVt&4v3Onn(|m0PpbUJ2To&eA zshe5-bip9FVEO5=m(;@My9aT34+QxQ+~)N?pIxQjMV|qb9CDF7h%RYuK&QRWfp5Z@ z98I7+N$FADw)G!w39JYOXOldlJx48gMdv#MWxj`19Vy)eI<7L-ePau2YmJF;Ve%19 zy|i1{Jc_2(aI*?ujA|Vych9m|Xq(w9L;JXMXIv0?SN7Q+YLU zED^3`9l|Urn4E+=3s>~JOE`!fzCDOlgcY4x8an^I#OA`H^j>&$(O~#Ru>=}h1yP2y zMVLVn8nNcRDUpEOH5w@KGtLiu+3irswrp%_X{l>yaI5My;TyV=`QZ}hPBrVI&xh}H z#9ec;yfQwUa-TK}5dq{Bc?jU}05c=(SRzXPB5HT8?X7>65{4A&mgTMCnPz?|>FO;> zJj^KEMg#F`jI1yVfT_GZkU*ad8=IiZLRD)f=%!y-mL{h3Cf(N()jg!@aSVBLsK_=2 zN3u>!2DqctNukUTR^o)#g0+k>%G@I_rF zyL$m)R)af6#Jr9jDH5oGy~yW!>XGfh z)<)(Xe_3=t=G9N;j4fL&f&BY>>|G+ z1TBZe=hvRy$iXVGs@FHhDL=N>h2dzDvTPmZuFG}640_EYXa`NG4KTAa&$i0CZhOP2rY7V2$oYFe-vC_-(p}uG;)L00 zP-7{T_hc=*A5E4sO-WLd|)o*QWUYuH5@* zpdBZCFZ#hZjZx%zaUZFq9==Y*#0n;5Z8@K!NE;QqPEIPA3}zbB~W+`%E_)Xy(5FX1;MC2|{BpC4kq8PXH0ngVmt$de#>;9Y3|@5vBc@d%g=# z%k3Q`UzG%Jrft#}S^K0Ge!oy{zT=9|rD&JpohUL?ee$c>hjTyH%$RpgBe!Q?+m1;( z>&tT$TbU)^S`YV)8li)W!{@muJ znmai0`+GrS-5QQ*z5vKuv6}j`TO1#+Hv$o|n?)AzMv$hiFfX zaiL}2DoQj@Kf4)$rij*@$2&=NDR$fnH?#q{i(5Cq7eNHWv3P(V2BDt6MtVBwuPAfJ z8~B~B9$L^ws73~OOO+IF=)Y7_9Bxf&{gyUyDaKd0!dNp|mA-<)0(?kEnp_|7B(f3E zohb=4MY15#VgSb!wdgK(h3rxxbzGh6Uo#@!_~~7=Wi~|2!c6yeqrEmEZ!a~YT66rfB9YLh!*bHG>?jd3$|vieqW0*oj6cU z8KtQ|dVfzb7s@^^2zJqF> zw8LK;x$J1vlGO2FRr5y|i{K6%7;%)f_p#YE8ru!x{Z0LJlUUnE;%JppzVz28PX>Hi zbl*E(%e>U~85^`3)p%W^Dohc$pI!jlQye}B;E&qnvFMaG@M+YVsW(35+I>m@YGy?j z7nE@F?K@F)a0d#%HhKe@28drF$daW4XjViQ6HN=oF;jmXCP=*2pLZqQYC=(JM)8H| z!s8TUC&3(yjfAlhG@!Fl~0DadA z^0m&E^^lmVqR)&igHEp<#pRjOVc$XYNAI|2AOuSUSx%-e{|%@?peKL6krYNai48E1 z7t^h?H}o`m%7phP+mx`E7i4%nZpg8AUF75QNgNs2g!FQgCGzp=#37O#AtmCs_q;iF zHaq|sTNl$Y`^b9Ii71owD{Rp8{zQ1q<>nw07t9lx?4(wfrF_73Z|>1rKfu0X;sG?E zEa77<%-L(+=b*}FLibAnto7_t zB^?g%PBg&)5-xUyP#V=H?7rq5$1m?x=zetpRTA~3oZr1vCHV9hLb8ZxZBtE*WL zli~QT_$wb}P2~y2mEr*i5I`!(hY35CZigU%hbQuz;1>d|IFvDK7I*K@%<}8HPG_l0 zayHajhAYoMhO^MVg9$eI3Cm|s+dDv15E;E@9uDTO2gl2Pn+K|joX2ku`lF$gnd{PS z-|hRR`7+=6%GDZo*}POHhqwaP2FAc(Z*|;&(UX-6sPZ02`kh8kmr9QZX6)N7VO3ob zQPjQK5&0oHmKSu^T0W-IC;znjNC3ID3_cB~Zz2Zm2@}j$mt(Q>&TTVo_U=*{v+ouM zJmu|l5`709G&D`Q%nxpt9K7ePasYw=Al&#D@r(h%i{%O?7iXQ&rhJ;dn*Vlo(hR#~ zM7cY$MlldN{>3sFZ|g?&B6CBe08zGgL_V zLUD^bQKzG{_-()i<5B~qzR#LGe*{xM{J0d(uK&-g?-`gZ3F-6HA_bq;(LmHY<4y!~ zs?zSPu24t%LsWb6!`%L4S37rSXM^BN3e|jXWf;WQx-Xw5qP>xPz;IQ7fJN8)jqZg; z!a&T1&9j-3Sv>E7eT2GHTk^!>2Wl;B@4MtXL&k@U*IzTxg>`B6t;B->faq%6O5G@m zP}&P(gDF>OM#2=`gUhJ5(fmU8Nk!W#ULRcCc^J#PSgp7OgRmxaAQ@nZ08X3s9ZKXQ z8oTyN-DaOMFHDeJ>{01%v2)WqL01IAQNikx%y1)R85rs9y>pUau3g4*0B|{5bRL$l2Qu8ET z&RB9juArko8KCk>tZDX+%}_@+q9`6*t4h!H7v0FRM)X- z$$ti??t0xc`i2+VsntxqhdHm?Nl0LU_t{@bRAoT8>ATR4wE(&AorK3_;!#sVG#GmI z9r~LaQIpC)X-9Z!`;%zi15ieKg{oUi@74v+bo<`ydBGR)$K}XE@Cj$qjMt4(W0nIT zR@+jqK+wDSlCwhPkvs#Xoh_9!z89-DT6vHk#|soJIdkL7B*Sn0*oC}B1JU{%ur7d= z|MUO@SsNHo#+(r~?Y9bSX$yG~SBP-8x3Qb=lX<_@S^)jU#wpKk7!fY@#g%hFol*=* z&O3LSfbc+)C6Iu-<+UfLc>w=AXicCyC0<~~ODJr#Ktw?3Ij^atN=w`L%^w%D9xq24 zFKJ|9D3-T$(@#riLS#Q0>%X(6hK$1H&!60>oAj9>vH%v>C-`KZBH11X!D!(I0H+cxa(>p7V6a^CW$ww)#c5Q+S1Yje zy}G*cr`I!-EFUu`Vq*_LC8y;beDJCK#seyk9WFK+h9Io1QlDyhy?1q^*Sq8~2N$CC*v|GUlO?!f2`v3`FlC(GSga4(>*S&*8IyP3tr#ezuM6mZ!LcQcPy2UgT}T(tmp=zT=z~Ww`gO! zXMa-lD`1xhr};5n+7g^U=NB-Th5BXN+tpi~(pqH&mxN!-70`tn(-9$a$`8oRUlCnH zIoVX@vzk3ELA@Rb<{_p(KTSn74%NOzJkJyD{iyxilkOnL0;c`4zu}r3^}*((t%~VgZt&mxd3_e#iu)mizYakDamgQsUtOGSrStCWNtrY z)T?bU!Rlavf%J!H-Iu}#AJG*d`?>-J0EI(2r5eE;fG^4OImrG?$4u(l+on5*(|6)q z=f6z@^TMcinuqZ4CQAdjTO~LfG#1Mh?MtY$Bw8*vmqX_RDuNEr%@45G^Hg>%IeU1( zuB3$gNoh9ZTG1u@Qg~_e2!ctw-Lk?lMC-0g^32LS`MY$hnf zu4YNKHO9L=WtJ>^-R#+#0_0CrkrP1j-<7H{yO#n~(?K+oO;QH1kz^o{|Ixj(UI5@! z4T!I&Cx26}omwA&TaO$&fS5lLK;U-ippazgl|4<5RrCdW+eK)d!}PwkQI&y*Os=rIKEPS&POghAaR~ZV@8HG;K=;qKo2HBhEmARGejAxtR>-e2pWY4 zc~Gt-?0MF6*V8CN6yJvVP3j+Br(b8 zh4ZMYmIVXPlHm`AL4O*b8mqEiWLN1okD!f!8xQc&6hIqMxr2vlJ^@oEY#fmg<6d?q z$)f>5)84qyh49JIt~&O%WoO?Oe_b2d!PhHBG$6|{^-cycB36MSNxf#5PG}dghj_kU zSA0szU-71nL;szr)lf4H*^R+=Y085wm0QaJXVnpk@LZI{<;8K`rW12S&vfl<+ zO?fKXj?N73-a946yIYvB#m^Wm@HYSPjWlMd0d|``6(CupaRGd;o2J+>H2ZLB`m3r=F3C{P#e!s}GbkKuXz)cv0I-y=j0Q8^AO5sBx;2s?yx#m!Y zcPdfua6I+kba)&WRU_X5xwK?yX{9bF`a?Y54EWq{fY1G)Lkm8_uqxAK&PMj`YRSf5 zA;P=ok8N_Ua3vIuT;jPJ!TILfQQOM0R_2}X8mr|qWJu@OJcKj?T(1NF3AnG;XLLJT=Uw5OmSGe3&K$l~8GgpR9MN?b@eB!^shjcO z7k~hW1zkkSzQO_}bKM(uaC&;cM@$M{g&YQ*_ zKwRkp6CR>Yvk~s6pe{TPea^kA92(?6z|`vbHn1saH!|YgFxOq#7anFA$G@XYA-65z zPiTEBHQe~K6iYxfe-HdJ1tZ)QBGXMlFmInNAtQRS8h zvsI!kiX@J2hW{_3*gvuu@VWu|pEzfQXgBUZ;xr8)RcAV(rT>5cb>S```COkx9Z-b4 zz`xT*JCv^iF!p?)o9nOxk8Olnj3T&v;}tq)D|hJ za~cHZ&T{B`|4H;&5qyR{Apw)2_^{|LKSwjW|M5@ zN4_o_cza(+AJck^i&s}TRJqPhx1Q#6RsMuRV+{ZftKM9a6xqyo4J>H$1;GpR^t_nG z49UM;b3LH`h9Ga9r@PUuhXQ0a@60tW@zP9}!%zRw1xro|*mZQIE>W`|ZIjI}m3>$% z;(+ff-q6j$?~ot}eY|pfKKCoQ67?!k=FPF$%6y0H#J+!SJ3917QydN!iuZ#I2KB8( z|8497#ltTX;O%UUGukR&$4VTebs?%UvUmTM_3LfvG5UD?9Y+o#$k&0QM{p?dGMnyA zGK-lXq4;j^<#ld>60$kl3Mg9`Xl(O*p zva}EcucUo0y|1=f)@38*vK1MhyuYu-VIz}c5OeXt4~b^X*U!#rp>*=Ufb&w$E8*){ zSO9Qv$I^6(GBLmB?^z3t6sP>XnL2es#}lcUd0jigCM(R`06I3ny#N7v8w~KXP^bRc zzq;*yrO>Z^BoF^&wdOY_Y#(5Ku5_YBjAw!?^J5T=u2h8-G4~J0YZ4tE`jy#ukz@ufnMu}OH9{a$L08a!4!9Aq{$X@GUL09 za>CQ-ew^^t5JE|N1mpmSm7H^=KZ)w9B3n(EwvMpxT!{ZOO+S{U$>(Lb{5kTLxU)0E zf*Vrn^aI*mhnrMPeho7!=n3+;U~OSD<{8j98fnnA=`O)!^TcntX-R-|LRg8@)*=eq zO#Tjgy=z4F=~ME4Z1C&l+4k&nFhHm+Fl$qUZ&a0uE)>PL0T(+hrz)4hM*&@`<0~(I zQnmaz%o4tF4#f#w&3*7`c({q)?_)f%)tf92j#+q78U ze(yx`9igAOD`kL z1va2Ws1r`P*8{@l(Zc<-smt<9@klDTe=!lIpf!QdpfE2PJ)c}hC`ZoFV0lD6FizX-u zgo?Fx!Af|TWxFgxOX24!)I~MVo4CUr%SgnIJ*c_vO%Ab$cy7m!K>Cm>yupo=%KP^FfhJi@V#N^n56)2+j zCaD0&nq&9mchiKSBgkv1I6}ev7JYRnb_{5VcIhe+j~inGMr8WLSf;%{IW-9VdKYuU zCYjHK^KKe+h_2%|{clUY^?XspD+DKP401tR8BojC4-+IB{cT-mBYCK_NxMizaHOf; zx-K#cAvUgwr4YEWS!K8?-*vmQN>}@@e{*RHykw!q7;f`EVwRz-lkp$&6k0WlZ^cp| z^STSTHTRoc<6h1xRqqdJQ>-l>UT{xzu*vRE+J%EeP{U?y2M3jxfJjCu%0FqI)OOCA zuL=#E3= z2LQ#k`~~G6blv^3Vt0X?@ERO9uSY`1kjVs=*ZlIPn^StH0_#P_G%gm#L~mfTxC1%& zs_l{JK+l2LRto@3nwi?1tgh4;dL7a5c8xJcRfUE9v8#L6bv60}HS0m3gV<&1AL`B9 zQ}}~0i@Ouj*BzYtL_Lz@gEiy+laqZ3_5pGKM_V4XKrJ@Z9n1;mCR_JmkZ)l2-axG6 zvN-e3{k7efpqT^VTG-aGE@`n^)%f}DEwN(*7@lxKV>iVr^HB{asmj$*L?4ZP3gyl( zuQ9WChv(j&Px*b^;FEXs@{KZS^3yb3syBd$b@h&JNl8%e(#{Hj_nzyd&%qfzD}T$z zAo=ll^-Smk78e7c@3l8gG2B9+xO*&#O0l9NZSsCnUr4iBuwD1_ENAOe2Q!~XjYs>S zLFm=bd3B4etWKz6$E23!Ua$L)4-1WuM_c zd!C{iC$#lG@8$`|j-e+}!WJn)M~PSeAeb@BbBxmf2XmIxNI1ar5wfj_ne_r8Tq%W1 zBOd;L(+%^U#9#as%w{DnYs2zr?k7w6ID`#`I7L^%uX;2nVjIYxT!9F>Q8%y-f!&qx zP^VpsAQi}}7$gUNvzWgzz!;{#ip02fJ179XQf?3wzO!rj^Y>Ye&=1%biot@7Z@v?L zw8tF(uP7y7xx6L<`~N^JxQvj z$IN$iiRzs>*&Co3Z6i=JJ+VZM6sNE5q?%9=tz!`h^K z*|K8lR8pGGr#fDGRpZLr(cfi$3eo*FjQ^9}3n4u66fh=7-~jt>GWS5vud!(F(wg?{ z`}HPkk1nJL!a;Ihiq6@Ia94%MbdD`>cZ2Jddy+hQ9YC6EG@h;bV-Ak5Sy~mZB(8tw zd>q5NVC=}neVdg|tjYko5(P-5iL&$~>cq;3cUSIC?d^+wn5S;N96 zNIlc~;N11aYa?_)zpbOF_NT219lY~fCrPUa5f9tIsscTv`hLE1El?^H_p7yDLfKeq zYF?*{$|(l8pzLQ$sc>RoddW7NfjHfsS0&M(Q%Sn}%2BrR{x;v+onrVGcDld(@j!Nk zF(;7#8VHbF8WWZqz02&EUp`dqt+ka?eR5~?f%b=Ip(@>xKJ7OIe;>r5c@+SgAe9+y zOJ9-3EFty8?ES1hn-T;2l}nNgsgoZcuoxJ?ihyPVlQW^mE$eUJY&h)_X6t-MSJ`Q# zMgyhqI^yb;o-;=W%AWt4lNi&v*|5skNZpKF^q8e7YTt;T{D*f zK(UU-k-;IxE_5|wJ?VsQ7xh-0PyDZQNIV_s?*Aui`Cl2#Avy(A>m3URlamLY%t%dr zL67fkG<*wR`rg30&wU_v!A0?7c&F;Y1HM|if%1n>n}+!~?UkM4;xqgML*2_MAF70= zubAfL*6B*e(%7z*Jt0dEn5PU}U4-aYRQ)xckrvibH&->`YpJnu4NFyWzI0n@(dUal zx_+QD>>4&X8jI)dq4A~n=VZrK-;prOTY07VHyojkGBez`TKiof^o5Ks&&o%-D3I+# z$Tiv*myE5^EPm}|6Zu_fp!0X$!!n@iuf1BD4jA8ducX-WNGiw^^#=jb^gBWtOlh0R zL>~X~59L6fgTBCQs{t24)5f|<>A_h;(h&2By-#OyodcDI$qx+iU!m{>EOS)_bOWX?ShV`1FTKv<}(RU5sWC?4dL<&<1$E_tlJIlda)?c;q8 zlT=?fUK%hItflBsebiCz{^|VKg>;7z51PII>?C#z2kRnIXlwec6B2`D z9VVTGI4#=NZR$GS0sRn*7LtB6o9u_^1QG5lUM+sJd~?=I+r;-pE>nAkr_(F?ozMI3 zJly`#g^5QQ$P!tk$Lak+owgdWJ_lPAQjv1Fl1;ddZcxb^d9)c4uzRxaLN@AZF%Q=U zdwP+VVAbL+U01%?K?ydj^V()uO78TweHU@%Mc z@CJAeL}TkAw9?ob2{V))#96i0?}!E&yHAfEO?fem7L?ySBXQ)+UV&n``q;qIe)!gq ziZ1d0b~*ar%!B_YKVO6YCx<0Va+E%b2;kC7AO@ra(Ywp0c5hEIk0b)*hBCzyBMtQ! zt#1&VR?VXkt}7giZXMhc=vuXfiiY+qQ2mH+%Zod=7m5R)XC^Vz4~X7SKbos7LEXc} zJm+t|((UVEw`_%~N7c_h8ZSgxBc&KL_e#&deiRS$1_YJCpb8XM$ZSfQP5t|2B@h2gv-Y>V@v zeQ&rI6RN-NDGZ63&sXa@@rN3T%cu7ghO=zs_bZcS%m>WFTM}nzGGyyDQ-kOR)O5$k zlbKh3$x7lYJflX22aqXyzQ5AuI4jqv$YOiB8pue1GL_By$m~x(LoPHZ+i_!O+$%i{ zm9j|sqt=$Pjs(zqxTF_E&poOpa_OnbiDRsJj*<6gj-0>eNr6DFpavfY)d!Y(H6XYH z%sIAeQ#W41Ws>T1p&7r!(ztIL{6|;qi+*^F+`b)uT;rhN_>1HDmZ5U17bLnc7z*L| zR+G6qlacM5fe^04LCDgZR37_X*?<+nmE?$mht}B^sRQQnQUsL;&VytH{+wH z*%JG27|SRxU;4PEPV+SN8x^0Ej6{I?oRe}D>I{&)#97(D3iGL&!JSjPYP29#*R z_rdCj)glPN+ZE_7xS72Kcz0k{Z(!45gq9O?LPx}~>|1{mV`J-1rxk~n=WJT`pAwjoFP3JXlv{Q8_xi~%0DZTBULU8%Y2^B}#LkVhbsr!y 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/RORE790609168-tax-certificate-1767252857.pdf b/CSF_ejemplos/RORE790609168-tax-certificate-1767252857.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d2f2a94668f50807fa92b14dae229e31e323c670 GIT binary patch literal 137806 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y z*E4iqP}jVco5wEhI`j~L0>u)sm6U>x0qpgz5nmK=&Mk!Mq}x0R58l9qrE=Y%%uCcD zcU0p~h+>MfJ}6Grg#C34B`swG`dC|`usCEcX4d6G{_-MP;t=peWWxi@k8L*)Yd{jX zFJA~*e<0{#RqPtolT~hK(&!s`w&&Z~*Lpj-R@#x=@6W>X^x5rh*YsD2_=cC4UoB_3 z#Zqiy0Qdap3LPG8Z0I-t@Y_yms{xV=RKhZy=kB#+*L3S=281>O4pF9`0k5+4yaJZH&*!NFd)M>6I}}!6ey5Q%0dk#1O^P{sM*Wc_!H_jk zwa+IvgV)$(hNy0GIE8m}wJ#b_Ah318}4v298Pf;<|tc}1OT?MnR)QI*J zA^9y!l@EB6!migxe8}Lvcp1-_jxObE23G5&-B=Q+UZ6K69y2of$*I~tWiRfxslVHL zJ?FT_iPbvyqFhv(&6^Kh3}BmWG#gB_h-xyW{oJEyz6a-NwP?GvBUd__CoVyMar5FC z0CI8=@qiq(ij0OytSy=L2|2_%mF{9XpN9pX$q23Lz2;4~n%2qd`|)ThhmL$6m)$G+ zh{iTeEX57P_`iRVVocn~C5h+&7?_8 zC|%-tdGc3($2QNJQ%OoT5afR=4nSuizvzlnIS7b8dsnk5Z6hpR^GW;o=&Oz96hFNq z=Cvsoy{_-n5l?7ix2<=Iwq|hAF2B!?(2D|4rmwf`;N6bc!_VAfh-5LrY4tI(WPXoU z#l>GaVZr%4gD*4~c3 zUg5=*3+*0PAYS-2*Hw?1HWk9j9fEh%CBpuP;(5$gAhBL$?#&Xbp=2%o#&@Y?z2oXL zV%6-IfnWGBc7tN3!a4RUX8NNlwC908dpkR+_k>2m@ET8|Nq$`Mg<}N6#0%ZP-tU zwE=dZpi#zEk=YrV68RbyAqGv*pGGBd_5Vlrkg!;xk*yscWOLoc=MhVy>J`ApFg_ax z={D!2I{ie{X+JD<|NASg;dkw8lM=ZIi@-40ki)RvbUPu4 zxHgy5iVluX(w5D3k9{sE=e$YG#>_F|d3b&M;Rn?$SnIu3ARb>$E|S`erpgy@Yt~+Q z^!lsw$16L9Mhb)6KCH~GJ9Okwk}WwKI~J4Qh#Zhl;o1GG6mhn1y^J#_>9-%)5}u8u#IGxoV=T;S)+D?&*<_QByE|N4p2nU)%-QYAXHtX-6jsiPNs{AU zs|`WLnTnyRg%3WzHq9~)AGmDz8~J|h?J;N`f=dCC5@CJ9LEkd_1PtuX6;VC%b>UTl z)l%D#_<)U|tu-dJ|NS!f6N}&{0}3n}#f9_ByBN9Xnzju7w5c=08b=XB=Rdk0 z@_7S=7cKyWl_!{MK;-}6QL*h;+q$r*wEn_E(0q#L7U(i zN?VY5u-cFan*fHdt$Xiw^uP_7idt2M@aYL8)Uj zF)> zup6fz;2^5x^uhze${T+jnFlU%N{$W8&MaT8U)4q}p1F1lBneb@^mWfah1r}I0acru ze*sPosp^Y~i`0gyHVpwa@@+d~WO2>GuX=>AolhH$1+IGnX$%{->+AT9`eQW5 zHcetYK=kI{QH}&fTb73Qxa?oeNmE7gPHF6Ex*7BYD}ePUYc_${-A4Z=DuNmZs70l# z-lrkkh`+DYNgvU5K&w`oY!8aG>uCvvTJSIDJb$!)<%qMn-;ee!Yj5q zFXW&ng#G-lZXrVbS^Td7_4c=Ghd*VIDXx36PIa*liG7Un_RK<3Q{{ClJxITMW%L zs+p-j&ba^WX~{1e=3CDfMR--Uc(tYx7h!w^WXhgesJ*Lx&6MJAHhy~@DGP%lS^B$2 zbM${6u#Dw5DtE82KxfxnW7IDZZ_i?py;T}LfuGce_F_VxOXj{_|Ek~qg<(S~;yJ4I z1<>n8X(k`1V63gk&v8|uJv5!hnp%(dD7=KzYy>v_j1PU4gp` z-8yq>+-sI+_nGKm(?k&Mk<=DE?p3FYh1eDQ2E9`}_iCGU6N7mt&mlzeg?$k0_ zl$)$NuevsPY*(xRMrl(WQuE}&F#b{V3+{y6&@h9HKR^2|r@r3k*Z96xN)$@Rv2>fC zr9P$sJoz^XMs?HZT2}!pI88CU^FGm+#Gn405CtSE&dwWfinQbndd(Pn|Sp|!T3)x65^S~(P z6@PV8{eubU4Uba3{4rpNO73hDA!s?F)gHg4jN&))n{*LyQ14Z@CjkLCaOO-~El z6#UEy;r!Rm=B6pW%u}9#$ydAIY^W)j``~Rm$thzNtzQ}LX!#WIOJ!1E5#%E_?u}rR z9`ysdZ2!^4&)igdWDdt14NJV`JrHp({UJ*iFziMR+J*#BRmo>p528ZUNAs}=uI(0` zs@1;ENO0cj>#O^(@rfUuKRV;8fPXT@qN74MF_7rrp*jNQO-&wU;tdvj#d774v|zb- zF#Ro%4!my#mEE1s2LDb2)wL0&qY9q71?t`}DqXr)^HjStUu8zQUFp!&g~2tDi&Z>W zdC#|K$&?~Xn}Ca>YI5%r^4d#xSPUX&Uw$_AsKpxHV@kX5{reyfRl1{+sJy2`06az| z(yh=0r8ueIV;jcfMYq#5Eo^uqc|+IzR@7I1teOb|V6&M1Y$vfO^`g1*@cb0Itjj3Gg#8M&M{4;AR?9$5)~~Cfm$PYNTve#-Ke!K#<@&M;@D@ zC0y?kycCF4!T-_yC|yL%h3x)EXV3$=1J%NM$HM4Wkz^yaFtNdb+Y6U-FWl?C^Cp{7 zWL@LF-foVrFjawY(#_4Sk&gW&J!?2=`=UvMwE9N%DqW-G;q8>ca8aT)uq(xZ_XL0@ za$!yf@csYyeICB#Key9x9BZ}jG5gVho&E}_{;F~m zl|cg~oq(l?UL~glk;-#5^`m-|*R_2c2K>igh1(^cwgdG1&mXz*iVZ!i4L>Q9zIK#vX;OHK!p zizEWPpmYmavf!@y(?CrnD^1PVA=K~ciyxTL$45V#Mb^|O;Y;}_%?P;U|JB}CfJNDD zjndr>f{ZjsOwirkASp4x(9IA6N|$swfBQgFJI@_6)_&oDZX~g_3ck5QQXf59phFEdWUY#7y2(* ztx{K1zgi|54_KzS?7SS9=!sstpS+7Qk6s#a!~Ye}4KK^shMwCa{#;*4{n-!oA3~>U z7g4wBzQnfVz`@{_Xxxy5{+MpEkUjvOWsD|9Ko>sustlrDJlOHlPiSp@o7*|Fl*vlZ z>)hmUoBa%p_-+6~_rv(*;q)$hk-h!|b8BkOm$0F(iZhE?)<+rfl}NcAZIMS9AvP=7 z7-uSzT&v9!r?>BMIh!Z%50RGX%k1hVexAZeJCy1073frIp>xQeG5&0^Y{E2=K#W^n zw~1yW$dq*N05Rfz@)q9Q6DsxBJ&N7zG^6qPaldnAvyrfmj6q3TNQa*k&`=*QyjDba#6fgYg4A$FP$6{ zNPwyQm1S-zKJ>XbWiGlx-u7z8vu6>i?CHR9Y7NZGOp3XRxhxyul<1Khki;{@1rP|e72$w(l?R-QGYEELNoN2-r<>mj;Z3YN&z~o> zj5ty0)Us{zu(aoQfC3r@^x5|po@8zj5~U5O#YmR z`7>hhc#*u~#4utskqT=t;MQ?}_7DqC3=2>GK6W1dnj1qnLW4EbsX=4|BwcZWfp z@Np1E7YloQ96$mWvvje#hN{AxZQ!;55FZeTu<6@b{bWIy6=8NZw(x5gghkp8?x6{DmvM1& zb#aC{!vTT_pp1*7i@T01)DjV~EX>Q!5~i&njgKQPj*s(8T79zilBD7omGDr{u5$NV z@iDfLhw^kY>e>ZUb(rMNH%n5qmD!7a_z~?3fPmK*UAh`h(9s9b=x8TAa|U)*vFSG5 zC^$*suBy}bxN9KH@T^|LO4ZSJkO`}Yw-=?(Ov7qU*a$pa$M|FLhDOE`vai%hiRvMe zdPZs0?vb8nr}>T(t+|-TC&AlLv3I^VFbzhs#DSp-f_xWlicv6BYR-t+R!bG39i`5JKPEG}S-UW$q#;aWwjPeIj&j-yu zF^Al_3W`+I!ZwT;MvpG0{08@JXC-oqd{AAsxT-ih(?Kk|x-~$a^+8jjvnN5FC9-EM z?Y7|C{0HH&x3X1!DlSHgb4-7J3*UQ`$7HnH2ASW3X+fQlt?%#W2~i z`}=HjM9i``)TJk%R=O4^q^<6w$(Zf0OzyxV7b2;Qi>ZxwvaAfEAb)BAyY%2@wM z;-icUUS3Z$t>s~^1VDd_oSyZkYXsRak$TBswi60tm^0r78>197kuUf3gtpXG0TWx8 zf+y}wS<}FZweQP40^NouA!u~bl)Zk;Y@F4kRu{BL*?@9bX-sj~<3V`(Ig;TG>E3g8 zht3QV7w`mh1J%k2i*B>z8NQ8j%Nx~*42|lSd7oF(3v$Ebv$;n5y|>Ya24_}LKL%TG z>>iPKscBh`ut)PqV|MqYETY*+Rc#}~j^wuTs4|WpFLAQ*i{){)VbPH>>WIGv5*oP< zsKns5KW_!MZ@#mCOZ@(rWL4VLp+fagsLsd}t-htJf$070ZBl=NO|%&@-x_kuhUzD~ zp%QQOJZH2wp-(F?<*?rkQt3qD=gQP<-2dRse45` z;>12VoSD=V@-Sm%>QdGXJ{C^e?Bu}IT(ot!DYrJkcrDMoGcm~ljo@R!9YM3K-csSwuQ&77H6%G3 z?gT@^QWBTyZ|)PeE|pB+E=W)A92(mUkg-P#C{j1#t|tq^oKIs^6RUkfQWBdr#iMjK z^hWxAbGF)_X`S-Sk_d!|<1K#AsP=>AJe`3`RU7+(`?$*wC?|Vdl^X~}WQD0aF1Sl$ zms4Eny&~b@(61(%H)bX)@e4UyG;hdcCr9dOofwv@ODG!p|H}pg;BG`C(M%m}Azja9z%A<(x*ZzvSE~rf~xKl@17=?^;4xT+YzfH|9 zGW3LMSYu0=tw#y$U1C$X%NuVYD3QODN5$&)$z9K@j!35zZ7nC;{9TVyb*TlcSEb>} zU|h51GMbE27E?5bo>xn#NdInw2!%RuQvOd}^L0jd~>FV9p5)zTJyplW;GBPrfIpTwKwT#3d=VND$grug1 z#DRo_goSV;K|)2CkP)Bn&($sxv8<=1!#xB54FM5Fc$-6#K|(=BzW)CFprWH*KNx6e zsOXp&n3&fu7B((678VW`CMFI(4h}9J;=#lwAjHQbxHex0a_#-v7vhVDg^6{I@%Nsq z*GR-zNK+_#sK^XRD8$I9#K>1&Nc4#0qa%pCrt{|q83h#$9Rrah99#s#OCm(_QBhG4 ziA6_45QTgn;f{nxj81Y3B!xk$1;u1=BLfE|=3_BRSGALCj~y~WEZl>!aVRLMsA-s4 zSlQS)c=`AR1ciiUWaZ=)6qS^9bnoct8yFf{TEVPsZ0+nl;GSOIKKFb>Lc_u%BBLHA zC8s<}O-s+nEO=5_R9sS8_O!aD_GMjtLt|4%XIFPmZ{M5#@rlW|Q`7HeW|vo1*VZ>S zx3)hV9iMzW{dD&E{L3|6$VjL^k@YKOKhQ;tpbG_2256YqbRna7BVJTuH1t~_3=%0V zOsE?v12_naOgb^YsvVmVqJ2ni;XZ~#!Nj}Fd~{9Pcgp@b!h-)>l>JKBZ*CC|^U!i=?GUguHlC^!KhR_`6pd{Kw+6CrMTl?&oSr z5*B1q=1lG)4tu34WTS%Tp%=thAkO%GLS_+&!;Z3KnLC_$xW9#vDKSF2PflI0hkEdU z*1B*`5}j;k5<)O8&+GS@h{#zrr!KFvjUY;T(8#tVK|unW&Uoq{lz+a9(fwYe z?*D{YUh_gy?3}Fj59iqa{!H8dhs-zsvGf&2I>Y9H&Q>0h>-WQ}uMe>P|KdfQc=LCJ z%>IO{N$?-KKcQpk)Q2zpQ!&NuJ)Enea~JWOOwmF#seklpB3NaAB2eoe*NMNzm7+$p zQ?h=G&hmic<%rdRC77zj8%!MoX{*Yz=aP;|)#Y27$#E&b^{ts<9|FfY>_J4CD^{+QD zLBRjsa`7*BFu~y8ZD8{9A(n_guNNWL=6_-Ta!kv4VL-gXy!Am5Y;%7MHe-_8|%GFW49FHi!J?mdj4;B066^Z{BF#E0CGa<@vC9 z6}Wq7Jxs_T6aH+oFQJ>;s+oJ^|HA(Z^-ricvb6kvv3>`E{*5I9;uRG5bqW8|HuAq~ zPdQN}iAe!Z#`8+0LR6^e^@{#(4z?&H8BOYNBp1{}Wf}9s&k@6w6_S*l@CmvpmF0M( z9u4NCn97c#@KQIf4#~%H!MuG#BZYBf9@F%sytE$V1-P~MA-a9XwrO(-sn^SQ=x=y5 z4g*aRz8#|xmSPUqLMCKROx?s{Q9tBlcv<_}F5G@m;wCu{nHW|j9P;Zv@pwY4lF64}Y-u zOtkJO20`kvMkbrOxf^g|)YxoPwfad=f$ z=Qe5$?j_u-T>!~0(7%wIMYxR4u^mf!Z+t+(yde2Fah6%i8dqM^KFw4dg)}=Qev%s- zYzz$6ZvJ>o+|ni4th*WpnD0O}I1ToVH%Xwa!rc#cVZ8TgDseU_Z~7*MpB~Rq9g<%V zF_hEP&tTWEwQCM#7hOKI%4ZK+Wpj%32HGH!zK^Xu_R;A%? zi@ru`-8YzIyY16ILcAW^6lA?bSdcz(&68nUXS>VfgTiEylE-v{CtjO zighc7zRUo8unl}59!x2XwW`38I_M`HlN(Qj(LD()@gE82O|iSG)7cMhUNUp=YG=?+ zPrAc{|K`@}%v>-vvb70A(Je!#A{<(9PAgYBvH29UHcir7y{E5#Lr_aANDe;!2JB&q zP<0#dz_s&DCxpzapi(8%g1fm!xC*zS;)8G=d~;RzKC*hzdUKStwvH~Uh@`ywv^53$ ziq6l8ZLg!rcx=P)H?d~FJjc#i8a|Ye;lt~W)HYASbVybvp23X5MJ_)m`MUEo&#c8 zm@*w7NUOxXbU8v)t+v>dc-eQCllO3ZA1m`{YtjdSHrCp3P1~tF+!xW!Fy1A6vapwy z8s!dqvlZwif0$%K6mR|x-Mbw}yJfjwG9tgIGX^Wx1Z6N}Wz?U_rS#7>x}^+EVePL) zdG*a{JDH}Jh3Y2TvQ{cuXlnM_DyNhfYe~0;6X~ug&mwnt=pbV5l9*d z=N5y0Ci&F@t#NBaOW0tun0pnsbw3qPLpxHYkcDIL*+J~WF1SjDAMD$i4)(a$Ib#!2 zT8U%K-@Of1T9`BGH>A16?#D>gURfTNLiPE0BQs4E{{I=%W&U`9mVsz zD?(OHMluM>3;nr|Xu&4ql&hniDvXnjq1ahUIqcT(Kp8@wSj|lm7FU*g#pz?n1Y#|` z(%CBMuQS$))LMxbIDskb)~2EoT!PX5!_M$Vrp^C)%=C?`aR^I zS+9U!Sg2qKKM=uP{WF&p@JEJdki?@bN^K!v(6*w!v{XS2`M|)R@yRJFW5Dz z^W@|kb(kttwaTn6U8hdaPw*FDe6e2v$V3e!$?kGhk>XqX6S4akw!vjmXb|OjjbemG z{Ws<>j#r1D@LAIVYFx!hx7g=h?m`*yW-G=#N@41M-YzjmN++%}QvM>bV+p;m-b20@SlNvo= zea_pdDrbD8C<|2^se1}zSsI|M?6q)k;;N9W7%$yW1UU-IoEd*KsbaRhoBs-Nk2!3g zkWCX&@nu*By#MNJ**Y zco^(s?$P`yQ7mgIl}2&&%2O(Qh3+VG zj_kq1OIs3N#s5;)#FO?|Gdf>0QgX1U#Bl`LNJFlF07yk6Afdb)-3JyV)NM~BP0vV8 zj_=&D5S5J<(N(RmJ|R^5AkX5)dDoMagmmF}l*yaxtOM#^wy3M*IwTQaa#tiL=2KS_ z`9Up<21JQRK{Ks{NKUI6#SpFRbyN`ij;1r`_J_pJ5a!~AaH%HT@buz$iqV2uD);Us zF4NLrp`zq@8?dY238yO`bo6beQdf{Ov2%{@&Nj1}6>ORP%)&B^j2*6XGEIl{MQGO= zzfPB)@I#rhse}aQ*_OAU@7XKP`S!<-1FVARQpIj;0w^4ryfmawNxDCMeN$^NI7;Z< zDMZ`mGFIAfgRnO)i7+{U>AGS@QN)+Z(n){_iUSSWi%kk*o% zz@u39!MtiiO&%d$W<~0$q8LY=9vdmimKa`5{`_RfX{N8|!%%nAV;v@SY;JsH_dSrF zr^De&N?c)p(FVd$J;B z<6_eyKD;TiMPC1+d3kq1;MDi!Lg?NK-Ldpm6LV3H)2$HJ0iABHj}Hqe8O@gxyC|^J zhsY#yZ*1y6@rFuCJaj1Hv3rV7FdM=D@$Sf$^MhnvG^AH-Ol0>zLwPTGS%ve5=!A1d zr)npW^6)l*K3@~s<3md5Unv=N3$d`D?GWSw(SlX9ofmfp_I&ASQ1Ep!kib;pF9aUD z-(hyjfAN@=+I};}!>^$V2w>W6G?5TJ(7O{DXuX+!1Zu?)lFAv3^?7gCMV`g%;jC=3 zD`Wjyiz-g^b>90&XE56rK{IM$+U3AWipgrt0^iUK0A@i z#gOV``WV)Hyt_7eqnHeyGT23&5g5{b-Yc^0>cT0|&}y_HVBj0wK30v0^<|+fQ>X}) zi+`UvotLqbJtaz%Ykw#|-W>+Adv{-aGV^{@hzxq9oVjeP<)6j;4N6-+nb^M>8xg5f z&?X_z5a39#&OgZI5JXA(aGs|=_X%039iFs}AD=d|SaKMH16KEWhCL|}ni>bPkP>Si zYp#lS-KGImaDe1^+Y1Jf z1NIVRCVkFqZ*92(6YBDV^_E5l7260&-|BnK$&TAqnDo7A7<12i-nsJiVAxYPQq6Mp zjG9{Qa84UA<2h&TIrf`FWW&nsY>Z#p*?i5X&hbs$Ya?+cpXuZ0Kf;YV)*q<~nvJPx z9mUgV?ej8|0gWisz1#*DbiA$Ye*UY2wVBj=RLiNEsp5f19Q|YpZ+pn_Cn*onQwC$3 zWo@PE#68ERm{M21J;!qXJx2-NP92`=d3`p=Umcv>%K29vd46)BG%6ZasHs)-!yP)DjJ9fbLZCTJz^;=VZTynj#_xNQ)7l(>==4O0tMmA%@Gh0Ja*&4? z`S@+`I4uvqWWPPCB@xxxN7D)emdd5BEr5j`7>!AD5iTVwgC&=iP+@x-M(I4Te ztM9rOX#9|n`n@hNLP4k4GtHdNY0nH*h?yyqPAQiw`WRFjDnieWs}{wLe^-;!lj($5 ze#QOe-BuC$M(PxT{%;k;+B$VSCIyck6zzaoD3+Ss7vhXtof@)Ftif zhLi|dDvrz!Rv6{ZMVqMZ*BadBKwI1!m_5;6Ct_-!H8Ah`F(ig>L}$r%LHE6Y+GX`Q^i6Nzk8ri;9>yBvXX zpZl;)^<-V2g9{=W?ClopZmQ3{SU=`oW7sS92IY?E!ITpy_F>IeeXd*PK;fJd>!rYL zswOO|CHvUasHhO-hmTa%Y>ylIM=CGvt(ykQ?&I^8pTa`8Dj8~q>-b@n7Zf&CV)kaJ zL-zN_%;ovQ!MPrYI_NpJ#n^8)bXVKm-VAxj0pZlc#d6&^Oq5ezW%}U_mi`>hoSXQn zqtiqYqQ*A%Sx>fMWWJpZ`6dC~V@HU){nb2?A2FZ%Q3USq&l3fHo;m*e=ZV)L|2R*C z@Cxz%GxNmf#ws3j@+55~99PJXleN|HK?gzQb@t>l)sNVcIQtiT1pEROBf|&NUPtVu zTn%0pU!V(pNl11Paw5c1-s(XjCLe&!jQd}tKT!21e4XB{b@xIn!0W5oRXx@Hi??T# zW&v+S))GTFHD<*u8emOacM}wET z{FR{+qT_cVb`0E{9`dr3q!oJpkYE>HY3<;PyVYHfI-#op`qjA$n3H4&G}Dbbq5 z81TunXXYJ5dP$OaH7)H=yL>#}2a02>Bo%huNN{k}o$bTgD2&8NF7@V(dcs<>C5O$U zws95cN_m3+?sirK&LBcVXtD}+_lPT7z|ziUPx;m`FfsD@jKNx6zr4ClQ3*x~o0rmt zkY~sSw9fe{! z-Ik^$9}v~?*0w$6J&p8X^ZY*1^VdqKC86703q2h1_08|-IZr&rRU>8?e1+51Bs7kt zntc2z*>9*lv*_S6(Y{wRo@`J34(-$$cb#Br_Auz~C;yai`i4gG>ga6F8Vb%L3v0(m zz>;p@WAU#0debRMRX&w^w1VtWFPl7PvO8~*Sxil(gfcF*r-t-tFoEW0o>5s%z^Vz`WN*=7{1 z(3gr>>tY4@90ALcPe0zmRQ1L>Cg|_C;Q}8>QKv>xV#C&}l&u9Lh1oloBPg-gdn_OI zr4oH8aKsj`Rebc^n4e101it(_nXYH%rNl8YtUGOs>19YzM}{Qpy`0zwxmFW+nF_OF z!OT!=yX~9xP3RuDx7A}Nr&)E)OlZ8_=2q@5h8x<#TLo=E|S1LFv|51nM#` z)(No6PGQ1%Jb80g`+M4c(gdy!dmPXzn#%WK~g2GT&ysE}fxGPO5A& zgeLc)sosiG4l&fSiay61B&m)9IU-u1PKR=zNd&;7FV%b?qD9h3Jd2TtY2_1mf^`wt z++Wi8%2c}X%s!z0y$&!S`1wJipC`WUe!Cu>Jg?43s=gr>nX*{Efg^St3#w{`a=NXY&d5780!De=%vVUfvNUR9>Y#e0{68uzFMWsWK)mVimnsz`(Q;;sE zFy>LwQrN8b36qU;NTauc#hrJ7*>#=j0;u9zvS3Rb-78tirOzQXZ!NkxdK8!0b zpOl{6&ZZdekL1!mkfBL^NNJpNpy1{lO&4m;tqdKN8iOP-MI=G`uAuxz3vvim9)h zIMX1)>w^jNB%ES68DTfJT9K1(tXk5uj<#%8oU=fwAkl_{-mXj%^w+s2(9I?p3$yiu z;2bZ`kBtK4zBmq$NIacyKMNm)S|*S`)_aB#oa3{6z^-WADfDqf8Njwg!$YgMc=;B1 zPUide{8`{|5)ck_?U2;ROim;1SLbM-C9i?;_p?K8L37fuGNAKlctW5tC)LK+fTy)R zeuMevIfaG2Cps-XnS7aYK!X|Cx4?^w=mW(ooGL43=p+?iXtzJcXzs|vsDMYM_h8GM z-4=Kn=~cxOL~Tc%#lpiShE0uNCQa8Tso5fMby(E z*Vbsvy6lpnLMv^y9xY`_h!PHX6tlAwz;y&R{OP3*?fQTLKm0RRdt(W&EVov)s537R zMEvqa-+D!GX6B*ueUdvla8E^{0+`Rz+m2TiVdON)MB@v|pbgJQUl5m6Mdt;Pxn@yk zyuZheI7)^IGw)C^rpv1myGC5;LnJ8tHM=<(tlDV!p*-(r%E!#mI&23DO|woflj{37 z;$%^DuzQIrrNYTkDUImIJtwXBIeA2Il6u-Yh{M-nh4(d3W7sMze6+^@qAK++agMxH zh=HY<>e=|w5CKrKW>#UP$-lyVv=v`XQ54X*%<0b) zMh+->$WzQw+?+9tQLFpKR107reDgvoQ~h1#8n*PgeSi>4`>q605Oh0GTqt-@J<=jb zZ*0Do5PbB;k~l@S;lPztp)l5?ASJl-E_3==OQ4KTQz3|<{h45#o=$L(cE1u}{#msH z$yQ#*7~NBcNcsifBzzq;xDZcum|#bi$Oxhmd^@4@G2Riq)~s?Ty`omz3&yTy?*l3& zfq;A*1;VY9VZuuCu-o+F8vQG}Q%fHNR!cR+U0iV+$fZqhY`UsjatZJoeq+pgL(%$( z$ATbLadzD=&>@Z60=4@s^c>gdQQ-)za7`n3VKZdLGRc<+*>jY~&* zfk=vvAWZ1$D;;p>o5N5+6xp+lM?oh8w@l1KnNLZy8K*q^)=n}-`$olm(NGezV(A7C zQj7ao*!-f)&*vc##%uh%7ZD$H%cN9n7m%GZ0%UyE@T+!m<6oO=y`YyAP*Z2UDgy^e zmdNc(@I=;UGHL4JcbwK7rGHL$2(a==sJM9CoHZ-LNH6iwAU!8w4TbDmRZ4u(7oP{d zmy;}2K^6}0dqXCYFS{?_w2wXbxMnvg(d5$@;QH}ZG8+?SlgIi~1@Q=li;xRTA|x|W zuXX*q57;k{iUjZC<+l2Mg#^IMoaJ3A(k_xWP z0j9&;!)@qlEWU5nCgj@sc0EGNZRN{}Y0qL|$nw_O;YGR4$-t?(&zEo0o|jO&#jDT9 zUtiwei3^CEPV3xV>2`4YY;W)Rsq>Dnr?{`Xn-}Dc##h$u<*;!XEi})-%NO8_C*S6~ z=hoZTEsiWN{e3^}W*eU!Q+9uS`QX$dC!4WHzAXE;}NfL zu^6YuunA+XtsUYqYhy7F1Xg_AS>M{JJZKIyB=tX=KOgm;yN&X)>+l1%_LKBJZ*HBV zvB$UX8bRTW(&X%rwcUEQQl;g}5EifT&@+A|++bDeCrEBmE@POc~wanbOCprN(|D zAHGRkCtw*Eti2A;zv5~sIa|A2`*C-%^0Y*#=do$HLoFR) z00T8ufTpK~qn(E>0?NnD&kbh(BNOVHyY*LlA0CL{RP3xg0LJ(@pzA0Rl?ZgbQF8r% zT{RK^-}zoHp4WV|KL&cuMEfmJM5-)xU~qsjfJakS9>Aju^MQZonaLw~YyeSF03s@R zAOQ3W>rEbnP#63uhHJWi(MJ3kQ^^Sk_}P_5$q5AbIZX&N81Ty$jFJ-s@N*gwa9+SK zjX=qX5Aa=F=(jL_PQ3?K4p673P@^Ob*nzz3-R+JD&8Oc3JC&#ghBWP{}djC zUq}#9ng8Gy0tWvnJP4Tg&*AY2{g^L^d;O8EKM@bf7EXr?f4dxd@Y*1Q5fxOmyf?yyB2IjN2 s%&;{P=UqV`?4M-MpE9sW~I0zm{oLI`CpW@b4Jd3>Dz1^!p-X8-^I literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/TOAH680201RA2-tax-certificate-1767252854 (1).pdf b/CSF_ejemplos/TOAH680201RA2-tax-certificate-1767252854 (1).pdf new file mode 100644 index 0000000000000000000000000000000000000000..8ed954c872b91277d5f96daece6caeeaf9d0ecfd GIT binary patch literal 139874 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 %dPV=hXkx}KJ8t% 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 literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/TORA0007099R6-tax-certificate-1767252857.pdf b/CSF_ejemplos/TORA0007099R6-tax-certificate-1767252857.pdf new file mode 100644 index 0000000000000000000000000000000000000000..da7221f73dc4b6c5d32c58bd40f8aedf83ff5f85 GIT binary patch literal 140160 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 z zXmXXnh$$FC^37G>AM{S6_5*WJ&Jnj2`}*{9V9d|NVCP7IQrsDE#PQf!&-_*{j07|G z7bZt{w71htU4}EePe(4;LTE&qV3iOTbFwY zt-V4RIw$Wa3iR(~u~Y&jF#GD)%MQoY^0^A5QABG`zT~j{ z6}uEcXKS+8mDBbo!mF5g%mJ&WIK8>7?4b|{OjuWybY=Of)2Xd;SYt`h(^F9$6GKCg zzSuq6J+Ft9b@GjBv6h;CVg1W?x07(NbAEMjiC6{ZgI%4v|H$#xW5Lddq^&*o+QKC+ zF;#fQSW$mR>J^7?U$RRYq4I|+eXrize>AFc5GI$hYGcl>us`T97VPjv^|dK(-+X^E zxmgM$#00&)l~vKvgE)|T4A`j)TJ^$)(^owk`?*HBLR?kow`7w`V2aPRw7Bbg<7@Co z`e1&@YR5BnlYt%2y#6++5tC3l3$A~;$T_NF7?$i54>0S>gM$ZfjBK7XEnI%~VEF}< zb@1TwLwa(qYq|J^t)q;WHC<($qM?asv;v9I!Jps8Q6*RSTu8)dsOnk zh{!19(h}lsuUc%M+7fx{PK5V0Qaip}d*iOWyY#ps?{7^*^h%ug7SU@&oc^NXghQmPzY&)VF~~{3K>(*?_zco$+(RQ!2kvsyAYFC#+-tJ z*W7`BWQN5-Qi7RQL|B(I5qmac-u@SC05ZmHa}$XbK0u>rnCJAbuMbY2N5rr>O^2xA z0IZ%luRN1PI3U0eh=9%sIeGoZ|N7WrI%sAE8rc8$r-wiMCetTU#t})m;=@)ez;&IW zJIN|DAPUSI!NP2URH)IQ)RFTm+;=$b{;!YrPyZCw>zO&k0UhyZJ*A>*^g~mJ0oClx> z&JqBc&AYPQL9fzcsJUbhZm^4)yd-zO{V&?s!?-~LXw}-M#nBC(q%(D0;B#xio<|W1?v8=mbG8BB9!nG` zwI~6+jqpY4kWjJE4q&D5XKaHBAJCNb;h;m%+eDt7;3?}Op0y2mW?j&?u5la%d_GT- z)Q=7h(gE0+7+F9M&KrvsE!e(&%dXwqf7rDCz4s<*ITlvDJ*U3D4lHerM!jw8mdAjX z*I#>?OeQ~=I1C%%avU8Uo``q8`toy;NI1$?IJO51*s+L3CKI(-n7(g=M?!Ta=b;z- zs=ln$xS%6Jt%yNRNji6O{Wo8KyKU#~$Xe+DrR3iI6Dv0^x3*rga*6ZJTeWhId{WQO z(IP}599*k zS#iHaX?z;611W8K3p_pcu3ENm?TS<0r4nXaZD^R=<{hi7t-oAiv*XmoyumJk;UP9Q zotW2^n;YcpvdQ}M&#bN2+dBr8lj!X2o8svk$WnNyV*8K3Dwe{i^i|pMlXLVCW zYOV3fV26WfbmG45;@fwRu2}c2we`}~D_!rVlGT!t#?-L$JAe4r`s?Li*qyt2W#h)% zX=A~TQ77V7g~c4%u=-#+AIVz0TRPu3-_Enw90} z=tQ*N;OHD)T{`%1sZ@hP&L)#n&tKTP;M+yk)+@H`c8e+M&>*NrQ0p6ZcGaeDKeb-C z>hm4mKBpF(i7H5M8|-O_JmIi*!55!dZ*sCbvES9>(pa#=GpJO|hF1T*_FsLu2>z3>vnkBU;oGkluG)VnG^L(L0eJ1Y)6!K-tgXLYzxs4wV2ehf8}k@N z4MxofJ;Tjo`*+L1^ekPm|Ds=}-}dV%Wyu{%1*fSw1lT{prHxwm+Va&Fbch_)R&nZa;2b4lZD`@504Po44GK9}9Lw zoP1J{o&8#HYd^QPUbn?5E;}|nAvJ7!=(s>Xi~L-HNzDk3YYZ~AjKXXQ;?axMGJw8i zI5RMUAZoRoq1Q2+3fv{bB}!qve7Hy=Xk&JzG~`^?s(Uob&bE^4E#-mjW!^1?SMq{3 zvMJuxi95?<_7w!Lrw3j+V!7 z&ko)s=Ts6_PDz51KSZl*Ne?X)v$&o z?ld9l^ANc{20c=jU3}m59BApW@5oKC?LFdQcP!Z}RK^vLCiviF`UVl5ODV0d$uFxX zRSb}+8j+I7pF#9UYh^=CK^eZ4a_CH0?*<#q(ua`50IhqYUZoS!IFz#bn!>W$g7VIa zMhS~&fCC~DLCg*(oMO1MzO^7MAv++lGC8LxGC9jXsye$^F(Nh*o|)-%QQXaAQ0TZF z^xFK=?4anxo4&chv5f`g!oDFy11qzb`X~NCFs)LPAD$R;&OP|-jo>pkLeAVsboXs9 zt;5VzXr>i_8F~FfG45WVLFk$5!52J=<1+;G;ZaU8BT*m~_kRIAC1vs(NEIpfLqbpA zh`e;CJUM5uwMVDW4)?RF3(HLNQ&_GN$q8aIu(}L*ESyH7qSaHtxwZLal(GgXgAb$i z@E9v@{}7mP;@sx)I!yO#TC+b7?BEpB0KKF8Z)80O5XT1S&6xr}!=~Cs!Wg#oq2t5P zrV#F)^#IO<{C~FoK^YB*>3$mk2)`O$iu-FKrmTwuEpglga81}Vwi86e;hjWGT^I1| zx&WX+Kx(xCdlVOpiW%%oj4#X)YfoE$=gm2Y=(uvJ7O-Oxi#$g38ckPT9%W#J_ecp2 zHJjewQ{KV_&lUMFxh5kvIw~o-hBAbLXL}c=B0f3F-#ZvP8i}4SQ49ct^H&>7-%&w7#wcXS@ z8j}W7hfo+$MXgFoA@s?sYZmAgdKhvy(oLJ;Jn)9H2QUR%p!yaFWQZLi;8f$VwqC)-sV^T9~yEt;(9#Hi4)~BVz z>5UD_XzOaqY3ygyB^sG z+}}ScIlH1Hi;9vh?{}vRZ><}NkU{~YL7G z)mPHpl-5{yt+mXnzcs$I@_tWkq(;T(vfDYde7U5(qtaK-uj{VxNW8tMGP*4)qmRWn2?*$UQBIZKc<{>m$^1CWdMH8k38HR#wGqsE*!L7`8<& z9>nS3h;k+9D4rl9f?yOUUggD9L4CBEy1{75={Fh)XN_?bSP+p!L}SE!BJ!Ys2?2L7 z6fv_4W2WLY7?Jb?x`qifM4}C0_nSN@sJT#xsoDwwwTRM^=A))6k_cwTMkdD-aU3tQ zf|)6Xji%D0ri!7cxpRW1+Ej>fG(OMFbUZ4iqAbK-)LgdHJXe^hD$K)R$7q5`59S0l z^?;7?sS#KQGgmJ)7x^KS!pu{Knfo)Hj^7iav?ie*(FtmriwE;*Mpk%K({Lr9%f zKXk{eewb;$H!J_5Rx1b6@29r+4@-ZeEBuBc8)0-L-%F!OmE(PG z7Wo|r?tC!9+8~_%n)rKPHKaPWlwQs8Unl2vAqEACqB;$qQXDcwNv2lDD)~LAZm=rv zOb;cwhaBBm8%fS}tIoPrpLe@F<7{`87ptpSqvA3;tNMB>mBL;|TSimiIcoV$N{Jh@ zEwryGQ^u)j$vtmSb8seJJ!;@prk-m}+fg02qb7bUtE&edsJ1PUR`D_wE zC=(K|Jr<#aO~E;(pIsN|n_6eUFb#$syO%MQpd)y?uEzBobUL8OU~&|W$z{WJ9U5ZV zu3h!eZ`{dI?8$ zf@sO$=631L$GMYUwOzYLA(xMRdGQ#qV<{?X5sO&FA{McTMSi-N2$Uh85~39i*QWWb zqSs%mNH|O>_7HMAF$}?TQ*?SAr+R5oJQA(-g z+#5VjyH3%=Xisjh@hXlx&|G+%T79#l!MmlvB<9|KNsv^5}p2~&3;p1+}Z?HbsoEg;mJENP;K|s6phg+ z(HfQ=Ny77Vf_@&@0o%G0u?C`$lsUL@jG!o@XAaWy^K!z&L$A7BS-WP{zutZ4m4E#G z^;chhu=5hpRdh%{!Gi7O{v$EMk$LCZ+5y}Kr0keb|46(Hp{XkTN3m$!xe zLa?*)P?Uin%P`S(JR<1mhN1?&UaQrr#bRN9e;*UrS&@Pgcq`iMJ!?wi&*4$EGm_h z-kP7^N|%XLPi}3I-#}1PBpa-)VqnvoE7Q)FMQ&^@y4Y72*j^Eis-?if8ez1yRs@KK z>U-NVnkplu{7!>Gt`IbIwIs_$)Si}5c28PG>WQX`fRck<- zK!p5V+d`!<{mB${2NE1%QBbWP4<>!GN zprA;npR(!>?xP8wx^6=5wI^?Dk3|w-=dE2tRh^)p=p=fut#XM#jCh?{f&<`*10Q!^ z#@~_0&%xNVpkF&r4zuLmS;QhgOTZUX(pOs8)JPo|mk6zq4EK}nly(a_zfA#lgHkFa z=V$nOdEdNw)6dT@r=XM}(HI^)AaqJGqv&2{U3agkO1&0EXw_i>K{p?jsGQo?k5EnF#n|Kz>o zn)q;hxc6?}j7e{1ux0CuKgjIvmW-0)J(XxsLw|K>Sz9MZ z_~gbCUKdy-K8EC(LZGQt{kGqE2K;m)f-VA2J5iPRnk^+zPk}q%el>c?FB|mLnX&` z)!ZAQXUPN&7%DenhCWKJh*>gwu#5@Z04c|0DNG@#XZ z1zKBMi;Ig#_o!5=jEoGCNYvNY2M#?R>=@yzcs&~z;*Jm^lgU6QV5h-g$j{Gb;^oso zV`5?%3(l0)l|G}HH|M|sNiKpBvR&d9~{>x7m zEH%l|08;rNkI@)?;q!kkUcBgQeCIZoOQCH>?E~Z3pi^=N>(hKZT(@mr0~&n4VgJE9 z%~XkYgx^-2dhev;)}>1qg9~rie&l9yb!Jvr(2l>){e0w>4QoJvo_~@OP7AVY}r?vcAxPrZ10ll zM)jz_{r&z`?|<@@S^EX=zxmGA9}fAY)WzO9wP*GB-!5JH<(dsUkKK$f7?SH0tg3)J z*S2li44Zzpbm>0(s}XsvgQ5rh{hkFbzckO7g8WAAFY1TaU!R}t8xD+~pJMnc5M6!5 zo`9T^Zssq#x>=MwW1+K09<6k6vKDv^T<^HNoQ7XU11;fqR8@t?q_T~Y|2J;*|2}!t zoGS=y3+G~a(|cYO#RV+JfBT~}*LMe1>SOCqhKP$8&mZ%DyVwOT=I zs|jc)U2Q85Mo>9`3jLjhLUuWaR?=8}fl_o;C1=1gEr`a5sw+}Ywxl1di`!iqzM(nQ zsWRh=5g;ABYA8<3`~ZVPb05l+UAli(o z>gDB?oSY2y0AR9v_iiJOIP`do&Zv)r&>S4f%E|(WfM^4H9@Phyy~iVpMo$4plai9| z-n~mSendCol+m$_?$6H729suMYrA8|4z*g1;6ijeJUr~`>I&wBP#)|ASn}hKKc>^^ z<>lomDJfuoW@ctrR~OMU_|#}LW4&77+_&C(YyJB5;J3T7vXZ!TbaZr4Q4ujfa0pBS zG5Sbc7h(pP75*KU(5URGr|vU;8d# zfF~dSf~zj>ZlsTUG5evN^zOzKm+ymGM<(I&MaA5%0{1VM9*?OYb0D-*sf-MU5j^}` z^FtDo5(?UYJq|1ZM&rFNTrXa9bc@V%v-2G=3UDt9!fzxO_^v!z#xVyK@#vKNkPATt zUBe;(A_c>Bx1)}H`1!rQD8Fm5sX5g>0)Qv=sxp~ddr;u(@%X5w>( zH5hbCl}4i-Q#4es)#@~wvGaz4$D~H7(rV>0ka_}`R3sL_8!C~jbqIJ` zqXxZBqflv83Yk!$)&jc$m~H0!D^;j<{N@g#BVB#x8Vu@x&e zZQ1ITRIz$*R_~+=Nl_w2QY>OGAi)Oq-b51Yy>}9rL&iP*V}KDyXTDyv*l|rE;zAr8An4Zwn*k zA?vln-31L9wmoe7*4%qd`SuEN|I7rqyKiH}G)Y9v(c710wtdF;r&tr_PkSw}nB*NbD0 zfZ~NNOD`RGm0y!%V`I;qI|mX&C=`OF6%`fWz@I;Vp2OjQ&m12gzjyCmaHnN5*~5nq zLqbCC+_^J7J?-r53@$f=!GIoi`}XZmKKaDm-5uP1e}8`)8yoQUAr6@L_|dF+qVxg0kK1y0g)k--rio?Hqc*?7EEevY;%bV_a!$s7v93%fx3fw zf~+8u_V#wT3M~oE2+9lU3o(?sfIjavr<(I!8wO~W*I#ushe<^adu~yYt&%Sn@%Aj z4~{~$gCPG2p~a59>+x-OLm#=-5n7Wc$M4Ne3*L3W*=hCp)FDdjhy)Y)IX;^Yy!FOU zSFQSR%h3n1B@+ZFkddswg5xu@lG4Ju3ok3?f2h4_%N+a%va2C z8~sDBezI}Zs()Fv>ht4{tXd9#qKcF8;A6MW-Weq|v~1Ap!2?&Bf0dGR%R3_vc{k1t zwAAt18nG(>@~x0;OR+OLP|`i!JHFsjH+r27OU}w0k}70k zaJ9)}48W7_Nhx7Dy@J?*qQ{HfLQ>-j+83+ZmYZ9ekifC1JfBlj$#V5C$h&^@*KfbG zYSnKxesw3ZeT>lQiP5&a+fIAm`*_u=AN}lC+i!)IkIzXUer+Yo(Z3+b_Si3MLvo5s z+d`SPpKe;U>IbV-M>2dtN-)A{^oyt@B2Ua=M8RQ6Ympq6^IU()zaX#|)2UP#w;24`c zLCRsM2nrNhJo-? zJD}0PfRFc{_Q|E{`y#O;=xeBsaSkXhx^-gp&)!|N>epYKx*6Iw3hcdQa3#IgE@ozC zhB4b?W@cs{^O%{LnVFe+%*^(fnVFfH*}nVzzH?5ha`PiMm8zstQA@Q&y<1OrYe~J9 zv=(~?*7fggXU-ek3e}3Q%PQAqUD%%l8OXvPLm48uQOJE|8-!{z$)^mNbbQRdEQX;G11Bwe!9a8V-^d4}PnYYvVt;kqzCWMy1M;wS zbae6si8#YJ01D*pbHDMJ@&^%;KyG(wM|jOpDG{!YKHs17U1kvS#PadG{l8?sUcK2U zfs#4s;<_9;KAg`FLw&l9eum&|=qr^5GsWrv$+HYwNoFD8K z<=3o=bladlzLJ$l6cZ!vv(a4$`58b8elZJ0W)I8czWZqCyIM&Pv3T6km^S)lW4YYw z{%WgPoQy)2x|oOz-mFSSU*zO;UtKjPM*Pv&iKT;6m6PeV_%_7)K0FaovKLZ*3#T6X zF8|rTH(%etti+V?YvN_MZgjUiKBXif;m#@nt;SK%{PmR=1h|xLm4c6?xTHdwDE=q2 zRecL@|MF^;fiCUQ^5JU<8TcYSr`H{sL1-?%-RDb*o~AW;vvtP-j>ZNzUA%L%3Gd2{ zZAFXK3S94S4QI#^>x8CFDU&k4_DN^%Dh~dM*X2ZSl~ybs?>=S7LZn_^kVj z)g*~(q!HOjkr2zdy&PS{>O|B(m=n-PElWxK&JVj|ZYj*iNfC0_+?4><;OpS3gw$cD zc17M$BpTQ(p3|4TlG^^voGOR1F{f3C{7aY!6+V^porl-gjbeTCl2(h4^A|C5i+h@F z1Y+)a#46tRNZGGzkHj35g;voa3g&TI%#@ooW^V%gU#>rRelYR!l5kUVU3528^ly%s zlo$BcR^;bQ()quqqh%j-9-H6(UdOB99bbMOr={?QaZt`|Q|8^cZ7!Lci@3PJYweMz zhoCSug4Zyjx45p9g$s|6o0^&mB8bDmoerCpsm>%GT#zylU)WMj#X`Yuz~WDes98Qc z=0Fdxo`?zvIQlBTn^C57M+co3?jkFnYCc}ZcaO_XRM}!)(9_jQM#7oYlpsLGOo+=| z9Uy7qbW+DvcCEU|T6z@c2ufvtxHSg-RE>90w-VN%qQai(+3yF%iixS@YEl!ENqXDv z^t~I1-q}0Lim^ZKnveUO!Ng6|!^6c%rZq6~yem_GQpkK8skAP?7HN&2 znT&1JIF~+io`-u`M+fJY(q_gijk4qT;P#{$5jJ9MOm4j5BnRD#Tlyj{)zB{%1YeLK zpb+`)Z3tq#f|<}IbrqAbWzje*F&y#%qD_!uz6{J_wNVlAmW2ymk1jJsy3{y9qufc2 zZr2CnTwy-J9K?$FsO<;|Sz^M_!r2uro{q;fS8Yr7YbO zX?sx-n3r1^8xs>q02B%Vw+q|?%*#p~#iug*gDVnuTn}I#7#oX7Nfn0+R@_V!>iZJv z3q+6KdF%cDG-|{&!{_4SqN}T$hxV6E6U1*GffE)i0N_Z(V+2O5Vc6MQP$}dDUSfSe z3_b3!fI|FZfL?E|I<6BcFmm~VRft>!$WI_PzVLO=u#`KL8z6X6P8m^EpZ ziF$kUVgbzxP|wY;2wYsO)PZh&ovHVL7@F4`nYn$PELKs}u&*@oeEqB~Sq z@wDIJ@?*y8>C_Q%Ke2-1b*I$wP?MmgIGmrKe{EeG>GIl8-rjh*25|!st}OY~h)K}f z+tT~mO#S<}xQ@HEyHBEmq&e{5FI5e>Bps3UMzI<)(oOqOb07276lr4JA1YY!6q z6d90L&(m$p^U@hyC2P6CoSdA>`LX!b{I^bsR5)t%aK^}}iyK81+&1ETFvfgO4^Hid z*KWICfzo_rfd=`K9A&I`qrIkW*mbMPS-TpMAjW}`=|4kg0-1a6K-{5g8p^piX;?c9 zfcPL~{H4Hs!xH1u2ga%ib=>_s<@m_Tnw*_zf3x3W_*90*UcQ!x{Fxi5%t{zS?$V^U zibA~caoFxnu3RFaposN!xsoi#?@#Huk{8wzYkNh9N7lserM>Y&Lm#fzETD6{r24vo zMC4#`Xrqn}iG!h#hB4r+&V@2Vbxp-60(0l{$8|j(8id8|?m0FG_xxSS%eph_Pl6CF z)Qw`n!#kskhI=BuyikjA^{EqPfB7()9V(yeuE|oP ze5#JbT|bp3v0RwD@@t4+Xpln1Luf5CBmLxh4}Ad2fXUKb1#-vPq$nuMf!41PjMy3; z-ihmOH77Ck;_^BkBAciAawNEd*!V~E)L&VihTN-aFr$+5*!+Ph4)&e^=i{CJz9YqC zJg3qCe(jX8PVcD-Eza^R%E!+8m$gs7YO;3(v!ix#rYS;4iBXFzG74&oqJ*#d6j}+YnrR6`di3wVds7HxOd_RBUmHFD2XM?b=WAy zRGld=1RVXbl%oN`gRoy5TYZ-4@OV-u4c zE@zl5uK*NoB>l^^rk$riB%nIbv!l6UX(D}UYU-0$^TzBWb1Nc!feQf+D5N;Vo$tUE z;4UC@K}%9HGW#}d4!D{ukghqpe>+1Y{Bp=S#FBoNR0uLVz#kcmQ)h|LO+w z3@RF$H9%8bT)gLTNz0@Tly3X={+O4Smlzrg;#OHnDKGo|?d=3bu>SLM`>V>G4>T;p z_pQ`7Kntj4h^OB1cnW_+|9C1}seIw5OSA1ZLJKq;fbl@k$lnN> z)s_Ahy|k{&2LCHt$0*;|xWDxz<>yj@e29-dEb~6oG$t4E2A@YVMw>k|IPWylmQZZ* zb&S2fDDG#5Zk37dJ_Iom{9QhJ+@TUj7!XQYj7f-Y7kFBtu+SpN5t)^f#sk?shOYu8 z{-icI6eB%*q58_K|HBx!qvm}C+c?f_^XzOA>`eMhG15Y!r4QZ-p4s{iD`{zDVXjSs z}{yXLRmSh&06XpM`57HCg$8ZShS;}@V7dlLN4->4$Q%rsC9K*@5ixMoer1Y z<=jV1LYocGhw(B&r3-EEgcN0FpzeLR(&^Bs?OH40z}F$uQTgd?UUON)$A%_Mp3Mka za;`~4M=eLx#2;S5Fx;le4%rT*^rR0@t2&7DD2cnCL-@+&Pl(bB-X^vD#z7(?-f6sEeEN$G-Kaf@b{_V3e${7gp{<8@uqa z<0fw-oXQsIWJK-lgaJ~#Tqk%;WUR>SSNZgXd#~Z7^ zuY?kI&jS3aQXAgqb-W1ixLwjZ?bq2fd>@7=o;Wph0xwswqb$D`7j{sIEvUwBxW5;v z=086lvgxO|xR2<^Fm>xPmxHH$O^mI3N2Q;Cz?eFpB@ZCftZcsT-MwI53wbGBAPuNE zXN^D7$6j@%P|Tc^t+$9=OdN3XaJ|2B=W3Gkdw(iwQ^lfi&*(H%lyCmT7@Yd8Y(>03 z7HuHZSljF0*i%Gocm1kO=IT?hkUQ#;vC#hTL^*`R^}((;opJu_WCUlHgWax5SyNo8 zsPwwHiT~3m{1fw{57q=Q$j$}Cavuw}mBhW?mKgLrV%E1S=>!twaQFmG7`~gFv+{o& z=xREw?*yzc{yCqqAbK5so z4PVBcmjg^*V_6^;4>?(RI#P!@RIuEOs6XZ0?~;eID!L{aAI(!U>MtuCvbo0t?u9F1 z)7eiS(ClQ{nv_lso9ZC3PD(`bX^p+ejf%R8}W&8UBBSG!h&f|LB zy79y@?DPj#z!3-mWF4(zqN1V?8OW_t3J8+I!oo&Iq?wZtEl3~M5(oy6y8U`DMmCm~ zxOjL6_pbmcNh3XQwU418$qQl}I@GZ-b`f|;2)4jl_9xDP+51PKakr^o+Lf$Gi12lX@k<#Xu*>7Y-qUioQy zqJ|S!ET+M$O9I++lN{t>imDXMKjA`51w!mB{ReJ^uV(FAa=k%92dcum%5^INFI7q= zp5>k7?5XS#b{T$|WS+HPc;F8BAj~AJ@2>T)Usx<8P7r4DZn!8YAl`^qXi!=w(2b}F z@?abB4-ZZ!TQh4!CIHq;U_by7oBHU$>+Grm+QA!YlwfNhcMSzyJ?Gm)0UvjU5D z08%ZaP+q{zN5GE|87#CL0l_?e@XlZaI7C?OV4Pl1QCJwHWnf@nr!JtApu8GTsvT6I zi9j4d+`fAda6uv?@Gdl=oq32m|8AJqpwQ6y4jdtbn-w9AI2f1!J~%eNa;MC{kYVv_ z8=wU84Cp_Clm>=}g~=80oIJ~rSy&I?e2LLl#Mp^8$&pb12=JTT49r%+$Dbl%)Z#@H z61ukQ1R9!fL~L1d0(Sn>9A^^)f0x=gKPN=4on-dSi-J zYV`)G5g*btQlB-_3)P@Mvo3S#!C7G4Z}~? zCRH6v^6+So43rx}aDhX-p*t88L8)32sNqz1UOaB7zKT3ew3MmdbrSac)Bnv$pvTW3 zUTv~$Sscg;Z-Z-dJmGZXME&X}E?aWlPch-M*jP*t-3G8lA6pm@XpyrnKk<8JoGf4o zjK3ys;%sbC(v2%PNh>k;%o)-{spt>} zq3CvJJ_#bkq|byi=8J`!qU8iyHMPpqcqRwKtoNhqR0J5M1UxtjViDQ(1!m8rbWLy} zX7J$#!J8v^h469e9G=1AzasE`f?XYgI+Xn-^2s)tgi6(DR2iTq|kP33|f$wK<}vi;k79}Fcy439jqxRCk|%V zX5>m+2tHP@f^8v94b$VosLDV#t1Ml~5-=5jG7ZWek=(x*KGz!6m{wqHU6N&XWwN4e z@VFdnQ3n2ti>#*Ra8&$h)S9Z!$6N+8wDb7VcQykbi5vP$+-iRihHv%DqspVR=10GE ze{w)j!*IXDj}fXYh(If)1p5ysh5X```128Xj0cZ!hwkzg%9|2#1MlsF$Tze*T7$Z~ z)gr!;Q=T9xDwL@QL>CjX7uz!Fo;kHMr9=nk%DLjEg{qFZhIs2`jR!lIBClG`%EGzQ zt_k~k{+cxR&L6KL?+xqG0AfBbe;7)LeP)UaPwg;SD>d@SM1+(91F|xkS}sec_X&Y*<_=Hj@;QIM)Ux-rK1F1qO`d>8 z0-LGW(;WHA)uu_2#TR*8V=ZJ0UTM5w{Pk{`ndglF4eAKvfGa$Io9#qEK0$)8a}sc&N{RN62M6V@ zkh((~IcN|D#02I9$?-~5unSI!W@~F32Au#r04t#a1(^)IhdD5EazaUoeA7>?g;ZNy zTpU+bQ$r7B0R<@wHr3;PPj>U&%Nbgk_w(kP81=*_Cnu-BzrT^P7LOO$AI<`k$2VYY z9Z%aM3?d_7(j*Z0;Ru%qm53dw&&I|kZ{#mT+^)B`cU)CN19R(}qoZRS;x9Th7-C2T zBqA+=fpDr5MQSA1I0^~QcH=xc1sD~745&sp$OdBCM)WjzO^81b!9rAd-UR$UaJxiU zjt&lavw0%5Ff!ofhkJV<1~3sJ2S{@4AW0w^{y@Oo`HaMbwWv}-4!k@(e{&WVRA#7< zATto|Ae|v(!A6Ps!bmhP1fW)c;&+GqP4b^MCe?^cL>hXDQOg)3Xx(QArph$0oiU(;RgVq?3H@^F0DKf8Lo({A16 zovF@HMXsD{1M#Bg3VnNnp6D2a%0sFk{gb4;x|fmfIk!o1>&UKs7*ZTRd!t}X4A!s) z9iCNcv51)-=QJF;`%Yeq_s>yCMF93Jva8aY2!<0T>EO23#^(i@K`CQDd-cz*s5j#| zGlnP%zIJb#gQ0A{D3RE(cw{P8yPsAgat`)uhFU*V%`l&GGjmU;96vWTebq#_lfW+0 zN1z|K8+$uUOcc20Z=%p|SkhKYlgv^GR1pZJn&_r>&H2cfr#bPM6u5Sq*}Y)7agQ4@ z))S*s2O!>=Q4&K`EIuOYy<Q@YL|v&0o8BBHop9IGMIOV9I)YN(_T#1lRlZ_9lnyjzr0+^r`_|Z ztJz>Chwy2bM;7ny`JK&DF7^AD#s!u!NdQDk_R=!QTt|q2=a`9Glj8F7_oRlHs=c?q zN!tFFVUDP95ZcwKm8q7bu-ejs^k7Wp{!+^_1g08P5lR6290A^$kJEWu(rD5+d*6>A zXZgj(du{bM=iiRQ`(V;_{rENbI2n8QyIR>4=q2k{Z3L=|Q{IBPLSLVZt%lo85!>%B&m1nrxqlqBE^02Sc!uLsM&j+V^(u#X_hKblEDbnp*b=dW<(9b6;Cn@txCCgtN&yK;k zKzbVUOwrOT@4goZ?$H}9@L!@m%`_PsVSh6ujR~IG;rF9I^?f84LE5CmuX;=vu=Lzk zP=RciU)lkz0XAS?Ty3+PA>tr{9!g3|pk1IZHjecj^gvAbGax*ZFo-qCJAk-`;~1&e z0a~64TmmSL;6aMzW>*mQuihHa%~0=li6~%=AAA`o63ErnRUT2Elim9yhLB7gEI>Q7&VA9K7Dr12(fd*zQxH1qkdCdzyEPt!~m>IO+7JwgcJ{PEfOIe<=@dkSEsD9(Ti#8E&IB7}ls2S_48;t=Ab+mN}P<%`_A(eb_ewgKqU z$)TnY=PElByZGdh#t)aILQwcwcKVCYJ-}Yt812`ws|n}l_wfM){TZx-!mI>8qEPzS zm0udx8{M~^j!w|F9+kyJgO?Mt*=px(8s3Ly*R9vJ;ze3En7)4a=m$>r??<$1YHF5j zwrd`b5%L74HmxVO@ccbxtzTr8cdCielNwiSQ}Bg6XMxXy2I6zsxrEN&XKkBt6^o_0 z1U#0QX*%m0TMM5P!<%0xLyx(_H&fmeSFb~uh~bTq?+<4z1-EsT0>_THmF!;#7pv`` z5%|ox%%y2%51wuxuKaH;gx?3k74nso-JQ0C^DNS4QA6QE>+i_ehl|tMt_4|E<}j--mh3vj>^NVWa2p#VS4k=&tL^$OmvaLs0EG21$ zPa7%l7>DgwFSGpL_pv^ovyeT7O4ht4juZ{H0m>K`yY;@)1Wabx$&0tlse#PnknZEI z2Xj-;Xvf=|FN2#|34*(h_eXA*7ihWC>L4gqJ!{?ik&Cu1UhZc@^HF{N*sMKSr=I(N zb0*c+dp2ASV_NQ;X>|w32V)u=Smy65P?yy7DqFZLeTTnx>|+(A>x9jYjv*po3vE^j z8rEKdlNYzi2U>nQl?Kh`Em09R_NUvs^x*y%F`%>Yc2yVa=E&B`S0 zz1)00r)1UCmO`Jm5aB7Ya~69^>grdBAE+Q?D(lv|k7ukgZC!R^I!?3Z z{N29<%sS-EbR%&S$zvR!lf43g0L`jw_t zL_l|XpN!M0t-IKEdrj}p#$4)xrx=Lyq$C`9UnIf1Kl8tToYb9bB+nS9ZtES$2gk2P zVd8<{zp_?>KZ{kV-ouMw=c}t5b2w%SM;g*UFF<0HYVS5PP%Gn`P;kSE-E|UFC7904 zTRt@%U?Cs!@5KjiL!>l>%&cn+l$Fzaz5RUpp|A4k$(0>{mnv)d1F8#I1TK|6+H0xx zQtUt;A=uYeeq{R+;k8FEq|1}#AxzlHLv=bzMl=Ua^x4kBGT@k-laqHUPaPL-6ZPI_ zR5dpT_Vp`g=i3DTEyMZ6cNPEj^E2uZQ%H#E48k3y%D+1wVl#3&t;8A>GLt|GD4#f9 z;_s`il@&|P{OI&FB{0F72xAQc`FF>nb;Ux>cLGkoRt z<^~{8^SC>J-av+8jKpWax(nsWSE{P2Qt+n7yK?cCK_rza#6$UKdQ zi3&`Wl|^kn5$oJK{S3Fij+%1P_S!ZFxzZ>_Xa;P9P+-6)tu@NIgmxI^{&9F3nFqDt&41AhMKT zG0IUrjdR$Np4Nbi@eFdMJm24ryU!puxp6XLG@5G=lvb4LXNaqvh?$3?qqL&fIcX|= z1}@<~%VVU>Hu+up?oq)5rLCUt^~=ZJpC2xkwwLwraf^-xyG$+tu6emFu0|FX{X2P9 z`R`3nQBf0gn5Ji(w-RllAHZR@Gt`6G>4R0Eya#3o`SH?XQc8b3UQkhlXw$LGN2T1} z7GjgNIJrnvCSf2V@Ao_NTM{;#jML8Ke*aw589u%G!;E$0Y0*J$Lit#lT;~3mjP0-Q zZ|A$OVVsQz4ImdzovwCyrZQ4Ip}N9Rs%itF%$;3<>6I6EWaF_&0^I4p0Q274*>ckrHFbco`>2t)b4)S?fYGFil08?qvhz zrW_UKXCBtLnr$)8soSVBZnvFXV*PP87-v8;AqvgavgRbBA{Tj4L8h>3d@XV)md#Jf zlKS#ZedwItj<2D}vdcDzl7Gp=$%U_llCMJ!cZ8I-HlU>EtcCeS3Gn@VNEE|TPvL%G zY3}P2vJ32eEM6Aye@2+%3G4;B#w>zq9VXKMn+l-_ion^$2~-xFk%6Nb?uP^7o0;dr z1SwCzV;2XplenyiRW53phq+V8$;Y?lunYyojtQpA_42V}E-oT6Lt`f3G=q;a0M)t| zN1P2-4($2?Xsaq(U@B$5Z+m>UdI zxCJN_p{cxtlVpxOCL$vP1CRs66#@C;z@ULXR7h)t)Cp;*UZIg50MV2Rm9}%uQy7U8 z1*U~tL%j%H^G^XAg%(Y7)qqTfG(fuZ*PNjTGJ`SG4NFW+6hZv{%h!+xL07DdCNZpaA2PJ{7A2+K~9PNYWB#Ez!5cg8#= zJDY@y@EMephI}~-^thD`jFIFY<)h*UyQ9Zqzg4-v)88NIJe15kksm<~1@jKW!KUrc z?;g>P$gn9Qrx;smaMjg{W*UQeSTZ@{-9IgxFrQ?lf@fHYQ|Ms`ty^h$k6X+QrhjM1 zGGPL9{#sg?Q*opk*X(!rSgll>{%R2kVecEr=`;E4RtmA7uK!7}dBY$ZG%fs}N? zl=^jjaSRpkfej?LU}TuE9`Aj=RMq0*lfhQ5Q1v36npcxg#~r{cXMGa#F^i1QykN?3 zH>6wuH7I%rjr{AcHFs)$<-W^m6)q%Py-IJ3KANR3$_=i>5_d1Ki%`}jR; z)6XliU^-^cRYh_7nuSOQpYBuL8Dk`{SLTo}-P(_2qeIj5uZ!9j5TUQK-|yd|2DNVb zZ)BNjjMBpvQ1gw1cY|~9w%T{wdzKeuBP~&bb?=a<#&T^5 z>%q6iom1oc08U%3-#As&S}=BdM;-_M$$f)W4`KNHEvW@P8q5NICVAGiCguRFX>)Fi$4*16Qq1K?bd1)s*^6qO#4_Re`mtd464N%4+Pvu z1SP~dRog}tk%);XS>iXjN7T!pio=%m)Z>uQkbrh)sf<~>y!?aMwf6`IZ(4{(wsUp( zConY@Tx*LdX=9+51Nse=q{S(WaNzTIqFs|rhiA)bcExOjc;tmgktFIcAlih z)lY~tn(s%s%G`-n=>+V)??*Mum9MnPMBx84^ms5`d1zjx_3I5{`VuKaA zs>XSbaWoaTsMx@u^ zvd!zs>a9S0z~Bm@wg`8dS_5|uSnB4=kvyVPzf7mcmFw*(PSj!YtF_)+s;h+iZiY__ zQO$$UJXTjeDhkPglTjfx5$CY~58mPlTwW0ev^e4VWs^PaSjUTo1aC`fg0$FbTJ7Na zk&B#j=%`DuTQ$c6IdfsBp^69+%IzBE= zI^q!PGhzUgVpR09=;mg_`%Vhtme%K~cK%VKXbFEzH|=JxktOf385YQyo)Y@%ym z{hG;9D(wxnB>cEUgnYUJRuYQFQdL~R)LvaN;{dBWaSQvm*Cj#3IO2!Wxku2Mq@w)a zUPQKEHQNaO6m%O85|p^}XBeijlEcpE?6xAQZ^0q0+q^xd)3czV+1`ne;-;$7jub<= z0No#bg&~6givLxtoEU(w?dMn&wa`#G;6D~(!t7~hy+wJDZf|V#c00Ol!M9*WE)qJ~_2?m|MC& zJ!LZ!tg;Hj3W>-g-6uM$ib89EC)1K(fK!|P!Ex-Y;aG4yUZlxhFN>3TXfi#lPs6qb zx$VQ_tJ3YqrKO#Up8RQjnXO$u8YNcPI?m5Q#7J!^(TVjF6~T$6&AY}CW*2$;p*lilsxTaawWiM$GAO-)}uG8=eE2Vd)srUO z(s5BL1H~R(Jl13bG*+;AC^F7$ywY;e(bH)^2FPiPRL966weFa_LFUgwJ&I{?s+n=z zS@}|h6Qv_%gw@4nWIJa|&K6o1Rc+pQ(;FY>I2ImujXf-whKXlDsDo0Q5EXdX+7IX#>WZB!cPlau&@MG6+r z$|m63!^^)88qPt+xJzXAq?t-+JD++N%TC6K9dx2*^Ae|?Ago-at9^!8Pa6BmcgOtJ zU3wRVbqoxk*Gd97Qsy5k6)m_qVFkQSlIp+uf*5?ol-BlkqID~B@Xf}b>lzR*kzz)| zlNrOJ{;-55>SKd-r4RtT~6G&^U*H0)P>t|;~@gpPwBG?IqB)>ku;WaSY1gKzry4A zidpyg=k%^@Ll_6vNUyS{rRS&i#8YcfA!UO=ZF*a1KCh8w?tB{o0ZOp3mR+WgN;rZ};uHf+)dPszqVk;$fxa%@gGo|NahcsilgBCZ*%Zd8 zC36&GJWt%4?N^!&cWiL~nTyPykSc$gi#Ib^a%N#h8Dp_X2^tzGgBCKsem8UC7lo;5Grnst2(~XLcRaHqkd)ggi zp*!#Aqoxvy8>Ked4~+>6chTYV{f75v^F+{fypgrY-W#7RWb`N$gjsyc+9OXWZHB+tW;k`(_cX9Jo^2ew$u8MemFnm zbk|m%MdMlNbGy{hcExhe%)Jdp>OY`H&c`XO$O#xZQ)Q$S+L`qbWd# zV?M>iQgBj$4_^-8#7rUO}Si%m^SCPXd`(xw2 z__wo%yy29W!9g_V$%x^VWdi<+(|x+lT)(*KTaSuJrT&xF-a3uNu6?B{q49HT8oA#u zp29oxe{UAeSr-LS@sB*Lm)6A^LpfwEqc zgWSsg)HReJze*ZnyhXO)*MojF77FN=SzO~8p|YZflzc85>=n`HgFDxkxrojA4gH$ngVef>Z9eE453 zss{lu{(LP^|L3dye>LZ8?&TW~dy>J}#>mmh!C2oK9tIG#HL!q(VJBiB`mYEN4?GOL zio2aL5&bWD0}EqACqP=+*}&<)vLvnbO^pE=V&+y(#tuaEBEl*n#)h^=|K!OS+n73; z5wWtfGBE&>>gGm(6eBYO3m_q3Y;J1iM8wGPPl=GZlcR#MgRrf&ovn?rjS~?EAXC`Z z%GN>IPTvr4VG&~&b3$A7u+3m{|>XG2SOKn55f#|UuRI*>3B2r%$J{$CCh z9ALv36cn)83ltm@0uu6{00j*T4Fv@Q1qlfQ4+8@W2XK(k2#D}-2>-gbW4L2#y5``~wIC85k59_uS_fP#TT0Gb2? z3#ib91ZX}eCAu4 z4gt_G7?@btIAr7$lmOj+tZeKYoLs^pqGI9_l2Xbls%q-LG&Bv3j7?0<%q<+9oLyYq z+&uz=fX!XpwAlafFCFDx!C zudJ@^?(H8O9vz>Yp55NvKRiA?zr4Qvg9{i4^uJ;K2igA)7cu}B2;dojLH>ga7{nD2 zK#{?~i5VeK1Qj9m?NNU)`9q-z#pl=cLX$8n-Jlyd%)(%hvh0%G{sZm5ko}(n7Vv)w z*?)ljZ@AWg;6Q-^4-XU>h#%HG@ciw~PB`?|17pL{A$HS(|R5`LRdwCwT-`{)g&axE!3T4r z)G=iqC5nvV+(T7Lc1ED&Q4;@K$V=w^j$A}}7TR5FglqYiK*&H_#VYcSU-|KSf_7LV z>3gbnFn;3LkgR)q9FgF1JhQ2Msn5=`ns2;6zT^uprqu~`EpKfv zrS}oREj3I1YA}T}hbF7dHN}$u3|gHUvv7r`_3iG^Np(uXt;!xSgAa)bHtqATNe^rT z_v{*)YZi*`olirbEO{K|x<2VObjHh51rPR#-X(`8jYt8>xZ{h-G3`a+*&I$J*%V#g zG!|{>w)C_<$y!&J^<1=1jcQ&k-ODXmj;nTeYZ8hoYU}kPVq5xf+)Qq6HD>Vm+dQ2< zw`=&;Tckw8DT&)z@G9Dg!c(u4#+I@Ry0)=Kf3Q9;xVp%kFX$e|D;s07-&$Wep)80vV}hY9@cVyV zpgGoC*ZYEry$O6m2wHm?*G;$|h{Z}FL^P+QYobE?Tr%D$N6E<>8*MeUxllhw9MXH? zA{o|fI+%R{-R%i};1*7fv35AJ7IWZ&SVs<4?bt~L-RjPy+J#$Z45$b#dL>-*pih2& zCHFcqA57QPqc;t!xOUO($!>mK`7A5w0lW0wIm}(d+1lg8m3^_~Dj$u0@bs&>aM=ia zMY23WT;2ad2&wB?$g6)Tzd4fSf<_=zx!GLKsmt}$sC9XZry&$&Ju~R!oI2^xR#y53 zf=m87w)}`?hu?#c)%1$7;opAZy=f+1j~U{8|_y{Rg~1bD!q+=ZeAGR<^lB01}68Ig~q4-@?sYlCKik4!(7jd zTCVg%q&(T|J`;%>l*c3G-AfFMn0_^uXrUv#3XXjpVj$_w}rY_FH@309w2B7QET1f6bB^|Ku&z z&woD$actwZ)e)Biit}Xgk|l=IOi);;OUI;DQaN4UzelBB@@F;yply;nuONa3_4ogi z13;Ort^T|Wjh4jR0=$gPT00nXB%WL~5x8nj#l1ei&5tJz0q7l+a8?U<_f;Ro_n4 zhj85PCR$S5PfK&!^$Yj$+z{pcNn8)9 zx6(qhC7h0~)BKMGhGvoG28KDv>D(&nlW_=K?cK6th`I?Ksa3O`klxpn4+(%(&ZB6# zCyg&ZxE6dNU-nr&dHJyZPI*~Pq=iU@pZ;T$KQqN#?m>-c2l()d?d7mWe4khCTz8T6 z5@PA>Xw#`UQ~7Ijw(;GaNXVcgXKU>=ze}TY(5aO=mws#o?_~J4M)T%k$*v`eM<4ui z>pUpLn`{18Lu<1fX?q~XQ;V`t7RDMdwRE1by77H?SCV66`6x%vk@?p3mTQ8#b#u|W zHLq*k>;!pI69P^MJpX3rB(aZT?KKhsFy-R-C?z&|!ox;;E@6Uyw4d$-u^c1TPF9mk6 z+SFZ*97D(k<`O(K-q<(WPgM(@!gj{mrhkO(E?WI>VW!uF#BIrx>y;_~>jo(Eu9P(nj&sTSY zIfx_Qe@Uo5{~_zWi;sBeL#1P@;gYM|oj6WoZFHqGkOxT&#OQZYM1!*U&=UCJZMcus z*{V_HOlh6h+Bt6nQ*{`cjTMif0=BagA15(Z3%0Wyxb8*1%x|LOE0wd}U+4A>Bo?dQ z-V>=1g1>gRrF)2d1uKn5YMjJ60;h^;n^eez;JOq0b&0<*pTn(lYEotiDmh8pca>u%CJE4}MHv3=t=;j`RPUO!f)6z3F2KPHrR2|Jx zIM+G|Vme|vh?gR523|1cH@4#&9XzOVDV&g*l2&d+&WJ!@|)>+7R_f7afMGq|j=-1Ie$b?3r>BHE{GA=+}Z z(m9Ujfjc2i04>hUto_2X&0|%&e-KX=GGcsyY??6sGJ5i`y(pTRax08q+vD&&BEpKg z-cSNh7pfj*^pbQ7`u2D__^wv{YZIS~6mwxIOe>%of-Wp+SLuwO5Y_*0 ztXU$s`;8WwA1Hp5ioAp<*Q*jt=?Do_&37GDJ5=A_6W9d~i*jhR=5uI#sgS0wk*Dvw z8kl+vIxPVhe2*><{6r#}J(z8laMRq{DA)Jv+r^qo^2OfG_ns)Y-w%qr5L_zm3!3{d z=l2>wHC1#!cSt8xwO2Y<&RsT>6 z5wV*DJk&e7;y|F5i^}xa+oN<`zO4)(p-3pA_Nw&hPl{N&{)_5ctU3M3C*XW`{-LV- znQqE{U>bC*UA|_rPz77L<{_)T@aCW%fGA(+R)DXA{{Jr7nSPq|F^Dd>MS`tzlh<^c z-uhyKs9a=$bWrV(oD?)bUbXX-WZTdgBJJX8l!SQZE7iR;B+oIZse77Y^n+5^P5@V^ zf4r?J7@;^D+@_{iQ;PR#w(U3uJ(wR4Wls3=K?%&Bt@5p?KZ|55z$}P1zur5xTJ_K_ zb-HnskiXTx#(NB6=3)$CL<0>8n$D3IX#rV!iJy6LfB0u{DPwG_ZMRKfxM|A0 z4Ps^+xXO~w+yARkgPQec3X!om`FDDzjp%&v}s>k1lyaT{w(Esk@-(LZ|s}L|@18&tZQ@sThiaXauRkyCh->H@F-j7#uC2Vo#?a)G`DDY}_Z=Q-j(Or~p|8?+K zR!jz$A3wc3(2iDI68MJp1B+;BO^V8cPf0YyF?OF{6V`uz-;^$MgDvyZBhU=nkw{CQ zU~ckr$%r=Dpoa zug&Kd!t9o95`V~=`1tuK8>_ZPdiAZhCO-F<_?D8b$A5G-&ajitT{ZdFRF98k8$b4V zw~9(wb)ee!w~H~2N)Gw3us~CNdx);DgX2%P`nRf?US!GGDp+w6CHvRWxk+-jO|o+Em++mJV^)13w#YvA&1%u)g=-VZEJGnGC|K954`-(1;DKTWEh|2w~e zoqSxLlp3JF;Ea--M`GG1^yIK-62N5hT67!~pP0Di{EhySHb%x0!DO9m@`f-xY3d@C zvsfzG)6Z)Hb5{=}b9sR+>}1{y=thY}xa@&p+KpzJa~4K-6T86usxpRvtfTI2)!s8b zz9;9r!R(DPwD=iJ^!71`7hz&EtPQ~~nX$Tgyvn}b!@Yh}=>AOpX)7L@Sy7QK8;P(1+F0D-TEHGwUmpbyf%N z{}>1@HRGDKEZqN;la_pRIV+_WSRZ1V!==BU{J(z5iU22&EozE~?Z}H84P*VFOZ|Dc z^%pfVZG0tj&Wi?5E`>4k^UFxJ-Tq=x6yIWB9!| z@=f;{3~je!g4#ZQX0YSUbp?LQ5Y;C735sQFgH%VO&VZXLjD5+{bL#U>@s;&-+aKXe-1`vdOhL6U6fpO3aT}(kLq67S3*{`g z!ma8%qZhsiyo`Kk{oyAmA+dq53#VzRH$y-f%a$o2*<^Vueb0P5m~CqQp3JFeu4_kg zpFy`?tDozCXG{3??D>{C2ZHK>{-9X zAM8Ww6?=(ex$T>Q4MRvCd$H@UOuypNxsLml$D4 z*eEnq`R|;rtVV7v7*#44yyfcDuE`gAdp2_!wxZ-g3n6 z4LOH^Kd4m`c&mnUcsVNgCYkMY3+R!-RmUjZ4C&{7=S~haiqHkA%wc4-eae=u2fN-g zPlbj8dzZ2?mjjy)A=s7P`nlSDq4P!poeq+0Kc#|o4X9rLnkXWtNpw{>1c$X>9Q~vH z^A|twTCZqpmFL>lZFF~SWT(=zsFV46f0?_xOJpW?9HndH+_Vb2^Ok>w0XcTMlI}bVH7=9CS7*@!*A++#iM2OZ6|kHMED$I}YOJg&Pp3;TV2Z_c<3C1khSi6t{v91K>>y)VY_~Vy4P@( z$v#1x@O}4W;VbNKL|E^+ixWjf!yAV0-&??ae&|}Cex5QNzLY3JeusoT(B^! zuiD*&v9v^$xXngk*PltVh8R3|$u#Sl_p0ovEy>LegxGOve3*J^A#jJoX{|oz>wYXT zE9qKm-FfuY47nmfub9!_O<5Dvj!0DHhTC0YdlUjyR^(l?b7hhvF=rslrTme}K1sg0 z`)Ok+9NIkxc2GEjv5MT!HNBE*Eb9^^{B~)*Sk=aT4Q?2eY7yxXgvZ+L5pt7;VW&|q zQz2m(hasPOCuVbyxi$9Nf)~A`7v%yQo9ZCp-&aOG zZ?o_SC2Ff{?hu0G_tgASVp!*t6F{kUzv^0Q%$3%KgiN<4keYEFX2U;*FONKPZcui$ zc>l%qoZWb2YF;6dR;0PcD>iA{1Z@f~r+ zBL2!VKp1vL2(a$Hh}XGm$~)Fb7hR1U$_+f(IHhXC*wYu`fki5rT;S&}K>f^;#+F3ZOJ@mDA zGaN0<>@Z!Z3i_1O6TO%`g}m_ulh>SQ^c$J~SoXlcD%f*@r=gp{qP{E65*TDXTW8I$S6k77LFBkaC&A;V*Gfy4tTdV0?@lE>z z4X?$E$$Tj=d!mr)=h~I)Ui<2c+dMWvy}02+xHPs@`*og&9BEiy0#_Zb@jdyhm;U7t zOS5a{!A++;gGX~R-a+DEH0gz3T8d-AyUVoP8Dj-y&e-p?4v46vu}|YwL%m}ajlSof`nD(E#%hqU*OM|8%qJGv~~>Ywr*592K~J^bPT#LUpSBc zcu-jtOf_jyC&ad>rX?1_wU?AnMa^2re!Z>%I>%`+1Dr$Ju{{d4@wvuV&|^Uo-12e>z1QHGk8Mu1m$it1`k#kmP0a zFaH+gp(CG=R{&H(#GI@+g0zBJbtJ}!Z2~{?I|4{Y4;?`&D}o8CRRM90j-CD5ZWo?f zt|cJvqngGA|8yK`(Zwr0q}F&5(d*`t=&$^4@7ywP$R^o-rJ#{0Q&7rrvT4DwjexUNP_xfj3GJ`abdfbIdHS47sY7&k)^uc=&E* z?#i3@=q4fqjh`8V?dw$m?6ZRyfG;xzaK#i%4(+|oqi!2C@yp}w5k}~uDxc*)3|%mk z!JbU!8pCv`qfRq8r}UnYd#t$Y^~o0_0W6d_l!0|Ni_OY_kY<|>&_5PMBh6F%_ zg}^FYZPIh|R0`oD*N^7fhSxrwS^!neU;K3Tp6v28KVXLvSVc#uw$PP)xF3)R*GJ!+ zyTebu-4So|j#7^#eE(qyQ(o}+o#Q!QIC0xy$L&Lb*=y*%2n z>CbDb0y~t@>zzehr+ifp3CEyQ9)O1u7x5I+sO$A$at~DTEZt^sEppT5*Zp$B4;cPt z71^+C2Vo$=m29Li1DtF#B4!`Zg!pz^v1lNvB6lR)Dap5Ts25UwI3F$}va;x1ceTX* zO8C^&q_DMPLbJsEOu64CAdr&5?8!16xs-xsVsCoz3xXiQV$@=(Dbe3T1x&d@#`B|Q z3Lq4(_9DiW;d=H%%?aq(((c1uLb_@qe>#-F%(2Td!DrPW-uL?Ve)p*pc6hBup}>Ot^TnBZ#0}l`O~z@g zS?H+e2>y`H*L5nmoD# z_7~MEU`nKA9JZ%(ejc`^!CM7Qd|nN3vNg#%89B9k&ZVVBTZ7L_mExQGQob<@52xAU zc!jH$a_3VCqf@y%{HAs`9RY3<%K8`n{LU5?)s@XZe=+(aH?h8MSN;e~mpb%j^c@Jc zqR#Xv?z;ahzOWk~yBno8cO!A?5$lORjI-HRuYVDM9J0v}Ka*zFC*5&} zXbkc)a5L<5xg}cXqCGqj2ji|+cJqgP18NYKRdzZTz9rmNttcxnf&<)#9b{(#f1QKKoa7N~)x-+%fJG3K=zQb98bt{@mk~p4 z!dy{en;lPnj`|cv(45<{HnZLtuAcgQS@z6@S+9k&aOa1CxK~F^A}#)N4;p>0|Bbr% ztwzLs*K(v-aUAvdKp`W%mx_wm#)A;hM+DX0JN@?uwp0D;8XD>no_`qRGwWMlYt943 znPfjjzKnP894x}~(Z=slRO2S$ZyS>`iKRrz##$q9sc0q*rfT{MrG>%f2)vL2t%VE= ztK?#3^lurE6j^`uGSSkZo=3$6vmgG*evr?&89j9M{Hu1a;F*n&__AYA#|BLr7w%E% z!9nxuK=JOdOh~Sm`e-MyMEkSLPqv&o`*zK_YXy2QqH=8SFXu_D48*Eq#N1&4-?t{Y zhx>;zM{*y1r!!Gc9=UmgFCcWGwSL#-EQEU-et+tC!WR8d|AJ3RJ3bCZ3}CQMQtNgV zt3tNO`YjCsEo!_5bNYG6_2rJ={j9rRg~(?EPR_)z3bh63(|PL{>}V=_SBPqXan6IM zB%vF{n61|7kRrQ=7HCVl@wYysTQkmVtdVhEA7g%bzimsqf#&+Qc=)QeHh(UsF-i@) z_ZBmhz~9X1ovv{HcnpFoeyHVY8SedSTM^$g>j}p5)q@exe`PG(x2q7YuZ$k8hh9hTz)O^C|`c@LM~hv{M#fPL$UH8`pupx zA*5j1`@t@&{q_1kmgO^=(_Wvn-q&Ti*|hd8HpMezY$%Z;<`DiN-)wk@0iRxn2CmZ4 z%gx~oOC1(e(@Tg;|2xK|XhzE{bEN9xNQ;p(8QMO&w$3N^C?an4`}XfwMOxgWcyH89 z%rx+goWK+}rt=5nOj(a39LOG_PpvEMgf-Z2`>|d^5?rD4*F7#-8^{Yep8DzNWR^3V z-=9Wt*HXw`n{#O?w7dD3zO$t>m=V5(Ct`NfDd<%nilRO3(`>-3!}rB&8?WD~?0Mn1 zo7m#iyI1ezbzb-vb3$Bs&Sfagpul@%+}FUzIm1#4MP2xHV3D({Zm&eiZdmo0YpAbn zs5Oi_8qD6pxH6nkGe5u-Yg8l*M+o0xS>IXHZPsCmy9b4^yQq6`6tu)#E;ed09ulr+B;GId@& zuxtTSytwEY&Kx~FhY=LCcNX*fe>n9COr4HiDIb6=LZ$T^{UEAj@;c+@KooH z4f+0r$Ztvl#Fhjn%!^rxXC}edlYJdS3&B&57ovz3@F%sSQ@EG(x7cNM28+QlNIIFO zdJKBHfGKmRLfQ@h~`8dCcmtNog-Rzaa|1TkqEcUTUjxs0lQSu z9#hTM*-`BDhqM5)QAB2#!ysMX8L_#A{-S?LD*eU=tj}^4SRYPZIQoHigD?>hnfiaW zQLG4Ovq+<7`Lqn`YTOamC35(Uf8W|~pMG)u<5RDq5u@D+U>=Ln1CKri@~Vex8)Pmr zBq-U*C%3F9ljBp+zYFi248)^fJcv>Mx_k1L9IFuCS+pcbX@;IZa)2dRRuD&_yOn#J z4gJf);S3;}&!oX9cIZg(&;Bqu?=={}NL>!pI-BqLeRg*s+iZ52We*J?m{NRJS?LHjvcU0>U% zpFOeNc@RF2ddanak)d`B`gwYf5#I&V1Sa(PW6(cB<;J0~J7hbquU`A!>m{czUhB}q z``wm}6IFwwqkHRqkDIohIe*4C590E7+W1WENzMOZ?gP1iMGPExr~6A|&^Ny-NExK9BlkDta2Yd8i`ymCJ(Xd3}$+9<961nmLTA`z>qtdyD)!t905 zFeWJ^jh!|mi{-X{hz$5IE}FiHFTrG^e+Az6Vi5MK{zWyY9=QWwiVO>6`OgcRSNX*) z5n^p`i??WVN<`ni@#3i$i_Z7(@C5U^)24~qAgw>l+r@{k>3-G03|5U<4^d+G&k6X{ zNnX9nap}-$Xx6hN@r%JX#NWM%PM{B4ZofZ#6DU+4!C)bf`_^6WMlpL#&YH`4~KZ1sU+9R|H#hFeA^kj)F~AA*S}saZeNAD}pNbc;Ux z-!&ozkiE=A=|HM!z%QpdXu5fm%Jo$T4^qsMR?7O8;VPnUPrbjj%am();M-^k_@or# z&O#G}Vubye=x5j?H>&QpQsc{5pJ&X^-8cq$Jz1z;h$J`WVgG&G*K*lSD@coV5cfc)6kbNj%>)N*5DC9MS`om%Q^F_M?N&04u z??WH$?q(R6AM&4hr}HrbuUa1fnDhyYnSPb_+2`9n=4DNJu*U<3SLknBOOc#4WdE3b>w^V7?FDcvL@cy~f}@L;A8NZ6<>%AX$@B5zyjxLf&18O% zXB_TX+1sa=#2BGUoEFF&Qy4oP7=mCl{)I=FS;yIF?hS4AH0J}E z+K-dQh8=^*;wFFl84+NH&D(`z(4~R@@!!!e1f0jeSn?ifAqUdgkBX4zTy$>I0!nnr z5Z09sCZ*H*XMg&KXb4N4(RdXF!qgyxMt=C4i~F&y}Erh((BB}!g%;lD#HboU) z2xHt$Vcb9o=N_gMb0h$vSM2mNbTj|QCogpR{^wC!QB>~SRp(=n9uVH;Z@xJO#Vj6! z!lED!kOMi?zRL=R9;&azuDsSQONRWYDawv2R8gMuU^_bWK z(MogMWXzAj&U~oBAcFZx6iACn7={vu$Ov%p;ORMJeq27ucT3PiiA=mfJLOp?^;6}| zCD)+qdVG&T|EB^oEC#8A@Gt|GzPpQW)gs7I%*NG}hL1QnZ*h9Igu2%(#I-D;HQo9h zN8CMiCRs0LsYRK6{dOG?Wx5S>ZUrYxWil)KQ*RQWy3_x0>c*;Kpot3G&sX6ys4N55 zUY>?#OS(7m6{s&S$~Dk4O~WY7J2iNML1y1*={`W zd%2-4MZyXpu(S3F5On>`EUR7;^r}K8Rp1j!#kaPgl&z|)by>utH^u5po9vy zrG?r`Ttp)mqtBNiG`yiw2opeb`+KNH?Op5MrO4Nt8q0jPHq%vE;lXK4B78#vEu$7~ zHo_~6-~mCmeGZ|hvaUDTM1vf=^_r$|wVxkZK1LQ_*y7h`%enuAv1BbfhVn4xsKAE{ zTU7ceLC2=8Y@on-FHfNpO^Asg6CxQCozc2;k&W`Q9tsKF?o~5lk0@3*NFz9WWxZqw@qw`demQcb9tX(X5aRWU)t%<|ze^+Nsh`BGV!!ls zx}*!#56i2vS-$yQkoH((OYutN4$a(Rz6* zbNkirM4IpDxKVXolHP#IqJmg!8@}&c%fdac3>N{tOI;H~Q%u!*oZ7B|Rj-W_3@7rZ z_S}kxedh*KAO%v8g7&2*GTgM;e&a$Ivtz zl;YMjDx2qbS{Wks(^84zsH!G9;wI-+UmaM`3Z)E$XSO>gp3TQOydR4nUbqrEl`!U<7^izkE@~JAn z*uWnS&zVS5nl=q_s*X{um{%`XK2b4gUAsPwj7y`G(nJ{QPJD>%q*Ont3L zKZEu!a@qY^)}9g=owKQO{(M!;{iLdWVks{DYoGEIJY8+HUVX-dix%4aK?K6?Du{Bx zp&wL6_ZZhCY7O~21!kX=CL-(uG~TyMnRC8Ap@E%gP$$D$6DX)~xe~=oLxBc0hn?`82#0lC&P1W7p35`d&50*=m^(uSdq8?>@?LK8(EfT_d zxJpiGd)(qA@1>#OH29^m4A&wkyS(q#J!I*Ta$P?-I6hmZ+NZc;Bs+U&a-4u1R}0e3 z0?h2<;)^?J&ob^n;S~Jx%f0&+=oR#cXSdOPfpg@{@@T^Se)g91s2F915^riD>HR3NTp3a5*0&`mjhMN9l{|IuM*)UV=GS0E> z+QLZ?3Q(=K3{e1qfs0Wj6TxGUxa9|A4}=28w&J6w&up6rA~HKtnBV=Qp8jU@sU+^e z8%OY^bpD8618c)Ce7Y}r;(1L|%gIatosqfG+^B)q11QQ@Kv5P42Ty|8!?T+hMEijf z_=>X@teY4_6m`l4;Iq#Xx6OJvjW^%cIQC;|TRxiu{f`oazQuqqI{E$15xX`zA1V%NL;Ws1PWEt%@ zO#peSJnDw(|NJ9=BR2w|!7gVrRQHv)h4EjoG?~5N`X{-lCL^S>KGsDzyLh*F*3Qsa z<4TneyvMS}qpZR5CF|jioBF*E@EFC1 z7g`R5P)!hR-Yh#8crFdwT2)Bq;#P@$RShqE-|+m4n655sxM;(PsJeJeQDHQ#=I}Ox zYewJ;Zc2li`zcVw`J$xcXr+~$g)Pdj*Ks1?>5F)nav;Eud|)uTjzJt^FSBUbXDWTp zNdY42!to8|NP1b3z2*1ABNGB|QCgqy*FawfdqmjdFB7TmT@4I7uKr^XO-pJQF-TXY zJ|>}B*K~+Ce7N{+>=tY5GTgR^LtO#>BIRmbS$9mdp5N|d0w+zEt9PV1?81(|2ZE{W zx8v^bn6NDWbiH0sHGR`GG?^V$F5x&3>f@zF7zYw#dk9Sy{~kY+bq&GQGR?U?4fsMM zS9M)oeZ$C1^z_=0VW|Yt+;!czpl}Z3B1x@qklISG&Cl&|LXuj}8xC||B~eR9dsPnY z#vn;ED5t3lgyO&_q(f1&_S>q7*&d=J^})2+Slz4i+x+`>fPZQO6YkCik|R)h5P61X zvtR6k2@@zrku{z_FVoFtzMch&#=MM`;8>0+OP9b7DE=*Tu04v@;WZGi2l^oWS};FspcS4TOe*?!AI0HFx~T{bNd7yQHI^fn1b6qP3eFT@MAwszmbIZTh+; zT}oa6eGuH@YyGFYId_hrfdVvn5^8Lg6pfylKC?J#!xic+>r+)xUKx2gPt>}uytGI8 zTlsZ~I0IE05I%rzh=&Brqk6haf$ilk&rI5t+0|d@9S`}rj3lu`UB!W3)U7WTiB+0` zcYn*6cj&XDd|9>p8}U9#(-X4$z{C%ZKL$NFR@}8eDhI?~f(U0Lv=ODhO;&K&=KN*u z@s|8nlKTi~<_y=VEELjthK&(X$P6TW>~vKsRNoHLCMNoaZj{?`F~O}8%7VRQDSBQa z{N6p28~v)^B)iuGr#Tnmx~V8DqZE0Sku{%V(1~TF1z@18gT#Er)h()5-^+PMTX3sI zyiMorO(JP6>>KC!2gydy{@*ogFjqi|4A2XyI$*%T6BvDu5$>D!z54}UI&K7BFRD#E zJlzFS@rZt)^vT?_?^?-;7p5>>8l*{6y=9W^ree#1PV$eXpQwV0p*6EnmSfQ5-4pt^ z|5z0BBsKj0+_B&zGw=2L=SrNVu}@_jOlxPSlnb`?xhZzgKtX|j9GrxS1ddqk{zx6E?3R(0#@!G{yfr6=o|c7c|q_+A>2 z4m;!k;5U27atK3l=L6_KiUs;a;*;ko$fEB1hq;G3p0PCEeS&Qx@7@D)u9~S$=E_4@ zr1g=!CE0^6$_&KP^Yy8%qmXjkCu|n3v^-qYvR&!!obm7IQ;Ai^=oxca<=~h$($df1 zLq}Mr;M#j??aFTaQ9k1mVsqIkt^Nsi3f5{uFMpryA<0~Jms`Iqx&h91y{=~8BXr;P z`i-UMGO`6OsthUIq|?zWKN#hK;@{{M1{?F?3mS|>?(&3ZK7g4LN@l<}(=nW9i?}W- z$HiW~_--=?(TjSFXJ&BGoM!}EjKNI|u7TBUE{;V3@@RYeh;IiQ=R%z_51>?ba*{KU zNTKrsUiJe2(&Q6R8P8o~qAHuyKF@M-($wa+T4KhRl5Bb>d|&r{nEhQHmU^W@lU2lM6EF+JYNTK%5EY4w5x%ufcPWtYwe%-B4(*Ia5nNQnm({vBb_&e#E(q8H#nh=#%;bc&f8~Z_DSb8q`>pRf0 zvEOtRfu8Qs)_*9fiaAn9X)FZB9^NRhgy#Y3;6#r3?6YrrLYt8>Wj)&A$Sr28tzQ;QZ{A|ofR*;l7^81msQ!e%$4rpG3JD!9 zz`HAI*nBX*KK`2l)|qk4H1H7%3i%7ybXXLlQnR_ z;Z@`m_-vpf+()vwxTMCzu>eG~ex-2XwpWoRc=CR$^SkccpQ=D02O z*x~*>75&kB?fD|sGT}vaS=Vey&y5Zw<{f?6Lb~Zof}_SM-;&P5f#D+plZ{3qRA|`7 zJi2hinvuKv8TJuR@qw)c_>iZgr+8?Jxf5lJQ`}|70Txlv&)9V)IT^s)p!9Ck(o(}| zIjz-V0ob9sxxdU-h>Kg88-om}Jbk1bqpKcoC@mWiKU3q*exZMY&pq5=LXqL}0C zjH9x?)r-V1z(sLQs@@ zn9~dZxq!4asOn_FknL&izz0;Hthd><%f2g~;86K&J4y$iJB#&iM;il{iR|#Rr=S0) zB2&88oX5~ajzZ4T1wFyZa59!}DdkD?nuNcyo&JT7vp?@W4|r;Elpb7& zZ2z!1_F%N;lzWKj=Sx6kEb0r|zLMSxyt)l)CT$N-G2S3HA2wg_^~pn+SbcPD6dt|; zu21hBOX`k!$JfhSDDEKg&ayd_4N~Ar&j+0SXCoJp7P_lP3z&V+QrgWVnh_+tzqHujB+SS}&3wdce#QjDb~)>g zL4$`W4BNs30M~rrL3Lk2a-jZdZXAP**5QWJth-`BAna_!&q)6x9+Eq;29Xx8(<|U;W z>D2&5-|VTh$E|fg96UseYm?6^UpSZHY#z#dYQK6mkC7)`pHM+U$)p8=azvu37X0;RK>^hyjHGx z^6<5C%F#{0j1TcH&?Dtp=2nl7ewIpIS*{0Ww2XGaH*@0?O%fJcuSA&)E)4K4%M%9i z*YK|RsNzEd#t`_N&c&dFBFlE<#fDMws^{!beeI3Q_wLUmSUi!c5!!$O)NxbU^yZgh zDMnkb2|!R6`+c{jzMnuim27{tXBaf_`Vwz_Nfxggus{%_ylNF96=3g z2TCpPi52_`-o~!P;id62^MMvGqOpKBF z6wAj$<2*Anhc}^&f4(|X*mL5u?T*+X3&q&q}IEz?w;64$ks z7B_O8`rE^$Gw?wW_Wx=mJAgA-1rM)GA_*v*ez3Q1r9T})I?N*MjH%75R8;w(C>86n;Tt!-)yDz?Kj?u$~uP-zDF5V zX&rQ=4X*#x>^mJqvPksD*uUY70$CD_CJ&+W6f#6`q&a*tdQ z31_v)j!&^x<#$^@^^}#K2$uTS2&PJEmkTdF7tJ3j^C*X^&lQ@C9&rsSO)5nCUu(**5;E6ttOXh6EU*4eO!Flbye%bUw1D4;(v;` z7#W@o8|ca@K`+dw_ySEUk1m#s?|RaBi;+NIn6}~!m`fjT0RSw~FAvPi#`EV5zH<8N zpZ6Cz0vb@4-r9}UqbILE43BX?Rip9*$NKH~iVgQ&M@>*-538CGC6#A58G z1AL+QI$v}KDIHvxGAnMN=n0n^^W6zh#J>odrp*4b@o2_|)^dfYAdvl8G(VHz2W4Sg zJFi=W+k?@1rciHFSdUVCyjS8X<7=e3S)}i6n8tqs8Ga_m{siDB4s>1^AEF^^xH8Q& zor9@mw5-4${y6XSJE_Yz;=Ph3FMd4x{fyIHk4)M9JHW?@$iFE`Yso-DAwYXy*y|%f zT$}3_srh*wMof2R7epk2xtS z?UOuu)Kxm8>Z0uaPvDuqLOifESM0b;BY4rT(JvUuwO{*abNc$^a*_VH7xna49& zm)*|xLE1LK^0CpIH(qOUW!-cC68-|1c+)@w=mJ$u)BlqfGoYs_okD(c*TT<>|H(Zg z-ajX-zo>=(`G4YQrrCgMgvRy}RKGT{efK|cw1TQ(ApUF&=(D`3=lq9xHJ2{zbBIt6 z(Gf>rc98@uXo!9<4iVaN5-8SN`twNF;!F;f*owZNJ+bRzVu_vXW+*UD(VYnY!L1BI z>U2m6M@a)o{vn51*QRgs<=5j-QPX!aEk7(S?qF91ccDia6<{J^7ePU_qR)2CzihlV z^nCcz9a73zKhLj!o{d+T+W*dask%J0u}TK_FVIEd$Dpe>hX9M+AHr{EY~oem+jR`i z%45)%^d$gqNw`cOnEJy~RFx(G9e~WO{}v+1ZksSTfW_-e@e*WL-W78o58o=o-vv-6 z0k&^A^&gsJ6hKlLqu>0P4(tF0!JBLU$K}!!L#Stm8|+<~lP~dn^=^*9X-wUT*f;!X zYYnBT9;tI}5-{oK4eZyMB6s@nmh;nV?+@=GGI!q%DXOxl9D^<(EKLU2J3<8?R@S}< ze(d6|VDWtB#JpOkcw}2}=$pPXBD08|Yrkd|+e>6pzl}_oc|{B*;Ce-iU+k>F#rjM_ zu{Q2}Ss4{|E;rW`(MWv$p8h+!a$z7Jje|cds{TpIl3Kj2=A9mQVsVdh_qb7Dp55P_ z8D3kDR)}yvwQhzG>VxKL#tDxmdFCR}rj&nv(mto6kS-UAe`Qr(85sX^x(|$+3x)8Y zj*4I1%CcHc)Wm&USLG0rcdU%2HcsZ`qo2Q5U}Amp+V32T`*-f}{RPr|rsJU)-8+O} zUTZ9CIHKUO>Z4Jq#WEAFbtBFwRjak$YS?hIaVJ)&f5XY~u3drj?^2!i1U7eSOP`E9 zt_WHdu@~oO=U;1Az1S9-+pp2{HrDN6BdB5<09n6R#LidKu$1y&4DcUh7vyuj08YS$ z827zWCQp}Z@$==w{0;_@Z%UY2tpscnw&kVBjita+%rUm)d%)G+&n_YN!EJrHY3C?RkJRTrK8g28g&fIe&*bh2KYRj~vy?6KG@6We-9*>MuMU0ibLl!X z%~|fcQ(&ms;Ac{>y5q4BVEyv#-E_r>*|ta>Ff|b)g_d)~4f|j@+3T3HmVFO$zG>GK zm3+VB;3S418h+8Z6dQJ)Dzb6jW{EPnJn$E~l2R1ge|AZ$VHE9oVY4)Se(HBrn1V!@ zp!t%7-}Mt0@XqojjdJMLgNqom>F=~!=}V<4a~qIS6+DZ?o9x!oyKzMxIzL`hm9<^_ z=iMh8%oA+cgLGRMH~y#q^vVt#&C#53ehR3qXqLpCJIcGSkYzQH&CUOq#hOo>JF#yu z3k$y1p}Fg7ltNT53~(p90h}*R!F<@;GokL<_RX|y-^Lz~UU|#xwV)KRC$KMTy{dEW zlG@LugI7=G)3UQGTrc|nJByczyx&J{y?rsHx~$T%*-lyn&ruJ7yjP8Ib6sh!{pf6z zK7aLvvfeI#y1=IV+K*02_u1j+RiD|fn#Hs)h`so@K&ykHn>~tjB|Ickp`G%;e&ARY zY}sVLn_N6J?^dr!Wi9>PM;?y6OR?R$(J3PxVN%sYdw&<-zOsm`3NtemO{-82%D5A@ zaH1pm!rJ^#dASsyLa+Y#mbFU{%e>rrZN}ZxQ&f&7g`qbSKQG~Bzo-mAx(=XrC_9sU zywiC()XQVgNmmQxHFh$4G1%Nq|G_Z5J!D>b-HY*xN1XN28OLhY$`Em@j@!11ZRk_9 zB$DD9gjDA=svr*`UEO-+4L-jLc7+y+LzfQ19tkWfJ&6%I;cI;^ghM30W11FkLBX^@ z3nQX*A=sDHCak4&U4X3hth8ivHEO;3=}8r z#&Ha}?w(t2Zl0g=f{b3p)s!b&Z5Gd~YrPjM2NI%gVh-jFRlOr!DDJntyH4Kra3 zvwYsW@B99a_dVX@_s{PS`KKAr^W67yU*~mR=XG95br{DozcBrz(9AW9Cc$M{S#F(0^y2@c zS;>si(3ekWRrB*vK5@YU#t1w@?LBF|XRNHZ0U{;Kd;f#N>|C zqt^U~bDO0HW@ONWjefEM&6Fp~Es%?HR^%zKJ)sBT)9oI_>q|pOUW6U4GR5zEWm&nE z<@dfYeTh5pAK6EsZYAqfB2fi^?E~Vt^0#V5cj_(SQ-AxVVT ziz9rLuzvKk#M&k%r_fs5Y}TirXGAl74iX6&owBhSaw)>pB!L?2P8Nr5{^PTzJT%O2 zw=P#+JT8>}>qBK8#jRCL?o{C#%zeLOlVaCuV{n=l>$NGfz9SOnNh^tDhr>jS7;+IC zdaHP68<#pc>}c7l7jPvSdD4~fd#<5-5tj|5LQ zbcN+Cv$vc6M+4V8gzD}AnE)1;du3dE63LjVFM9Uz14WCp4jP}dQ49lsK&1;fZ=eG9 zT%LQ>0a*BTfQ!!`MefX16yv=m{FrPKd8T(E8@NB$5JB-YPaHPF4gs=_4pVo7(jpf# z+ZAjj0_xiE>2v5P#HxLH@~ndEWYS(IN7eg9``ZgN|IsL=D?iCAKW?X8`lczt-oIS% z-PxtMKBL9Gk^qB>PJ;%%Dd!b#GDpoyWM9^j^-cG_d9fyu71QxU>4sEwWqV|(pWhXA ztU7%?J(UnD#0hHP$&%DRENiZX~F$d=f2QJN!fD zkHbZDBbhOanL;cR2QJtJQ+(YVwYUOAiuFA8rixU>;8y|`-ms z%V=1;<`}!XqqC+xi`!%j`FhXhbKzE}%cOI0+$~w@{T%%zgCl`k;+|cO*ie9oyHK0?I;$VXAUU!35 z*}?l;8s zm?=7}DRHvdN+4S$65b~cDF&~u=q*EUf}6j{<7E1IC`b|x;Ikw$MUBkwL(-c1XRsHZ zwK46V|Dv9NWK)hqGt&9|rJpNke2HuwosqdcZ#N}@DIBCKkkyy@_s$c;`^`pocV$#$ z<(Ac34BiP8){p6Sj_N%9>iFdR%{S;xX)3)axT-3g9(Hq)NxC~sdfa!9k^H3Q)zkK| zN=whBNihNbI1wZM7yQ|m_VXoaJ~P_7U}6OAEZfOXj2x{T(e z)FKx@Kp2oulA?Wx5zCMmSr4bbQ#-yP0idJ9vj0x_!lfKZnikIlZ?zkV?kMxB;{?W= z_r^J;6ui@>WG%6e%@-l$i~WQjL~!3{Wx?GZ1xoi%?%P?pHha4npUXP4>**_dNv8NA zv(i31GYYmW$jpVB~GKozyDDW%`!Zwj#Q)gg*gD~9C z9p4G$L9^RQ0F|j7XiYYRDTMw1{);=6sk`wmX0bx(w7K6S2~?TX=@&LY zmOT98rqLmBHkS zl#w$Lu)h0M%Iu0as+&{G%db98?8DSbQH?>J2vc)&awnY>N;bs#PGI2c{EE-GGK3_s^9Q~{w|$`g^Q2xjsBI61gUby>yO?@iJ6EHpS{nF`3t zxX|_Jr)YPtS%WhtZ9U0WBHaw%NZHp`keq$Zyc!aOIy%op%=}O~R1&E7(4do6b-d+ucv?q4 z9fOR#*t1v30x7WZ{+%k)Bce26kSJXkxZe@ndZRw3Oqb#EQ$ig57ls)dVX=NicG@ch z;dH6K(!qzn3B-{b-xW8z0PFQ} z1NnjkHHZ}61WiB+Xh@Q-la+swp8{t9W86Zta^SZHwT4s=odZ)r+uyj)A8OcX&Lm5- zxJrb1zF9^ra8cfu=`R@Q861a{*la4d$xYrK^W52~sMy)TI#}d~pYQSN0$ez#7Sht3 z>L}wR4w0CUo#s?Pp>*m(7n_1v$f763ZJ^P`Fd);2a{@9~qdP+TJbw5lF`Fpb*Y%Je# zLMEAS3gqUp7(j-|KLGKFkdAM_u9fJOQ)iL9d!kY5fxE4K72i_yjzpP08)+v!eQ!Pm z)JV?cLJ!Ha1UybK z=`CjknHGLM4T}8s3qHxy50}3gG&dtawVB)mdV2T`z{s5Cz=ao-DI=M8OgpY$M%LCT zMQps#I$?ulsmvGyc@>O)^DVecmMx?M>P#(?sMpRIsAs&dvG--8-k<6;*Fv}z_x{<+ zwaqlx&D=kn?11+)kNX+VY&Dpf;<3!mBJ~5Ca?8>#LBeU>1+5xvjB5PpfiU5u^6>F!bi+ zLco#Wc$hC(gdbL`R>tIu{5yZ8YPP~;$0|OGzq}M~W6)nRbCHp`C$A5qQ-+VAGJm5K z$*hl2c@URsn)l^J^Wa==$z@(Nzb7VMC+(?{+8aA)E4PNTTT}jOh(@P-%!eoG1#Hwo zx&F6>r(^H`U8`3`8A5U+@BghwiGs}+9*OL{MZO3k^cs=gYX!!woSA9P&Tq)A+wh{n z>ifiKm$Kv~)>BQZ!msVV^>Siz(K7{925x*eVSgO%N!D5pNn#;tF8#i}l)$cAH#VVx zt9i25cvI0)Mk%%QwYs$XgRr}wnq%a}(;y3e z8LY-f_Qv;)<*D?WxPC3-7|9nQiYtyGXX2=s=J^vcxz23xA$56BmJGOOf@GxqyZ2$WMJ7}>pd1}2<3 zW;)49#_F)>HPky!x_HG|p#fy>1!J z?#6Xu^lEA5I{c7fAd!8Z=U0tdQ^z8+zA$5&<&EkP$i?`T_cLtfN9?PH8>xoJZeyO5 z`RKn|q$ddG2!b=ERPz$~W^H?>NevGEMqW8Cr0K0pCfjW#5%pIc_8*)DoUftL!0`Zn ze9sI-Rr~ege`H+yRbXhQU`W!Y{-9pRul0D}mXEL7o_p5QFTv)eJ_u03%P58eP6Cq5 z55n-3d!xu5n_r;a&uZc?Q3Yx?O!+wAR*K=S2{^3B#84nVbvXNwHzGM6-a7mL{0Jmr z0pJmX^eqr*K(rkV0;bPIQ3x44cp%X+?5Cz$PX2_|(vg#q{K(&@0Xo-TMduO5dG#~v)I2MggPk{G-!u$ zQK$Hd{=_Kr@-K}f%JU4}q@p~;s(wAYE?y~H{h{SznuubRSjN2j{-YIusCr3yWX1M0 z61Oe@TuCyfiBkujR+n?&_CM!%tTl&P@>YFd0Of(u&rWDO+s-hf8drVo}k zjJZv4`kT@?LuMw= z$b5qFKBVYHWoWNJ-Uuz7RrXIG>7upoen&p8zsY+s)kge(2=As!9|e;tzaRgdq7WvV zj*Ec;k{9ZVH8u&cpt5dn%Xrq-PO2vN8F}?2UDm$YDav^uzq1V(^Pgl}{BD7#7yhCx zZttaUc~stc3BFH7iCbKXmxOX!$0Ep*MZwe@aFLI#h62liaH`7;$mKx96A7<_N6$V` z?b}~daxsfbn3oWK75-4DP4i@!pqttm;Tzb}90`X_FNPbX|IyT4jyE4$&-%lworW}h zupj%#eYHsb>v3z1;XrjpW*VH_28MG zxTW#uYh`uBdU;M5o0+MXqIb?*n-G!Xl zI`>Ouo4!?jMJ(x=rs$}4zkdg#)Nr9k?m0SUwU<{bvs8|q@sm`hMj{@!{pcN0b)_51 zraVhstJpKY-}00?YtC2&vO|BTlgZuwII?pg;wElxz6R}<9Trz;aVcZ#gt{h+#r{HG zY)LNF{M3Gl2zD_YJ9Pv@SH~a6#JtjjVnsg{SKG+Am!Z|~pn(-yqW*GQq}CnHN~63u zRdy7AH-E9^^Nu~rml**#WQV_PF zZ;CvI9`+l?ia{-OMt{)1%&QO}&y4WGxUj?o$ICAF;ugBwuI)=NY|)2$b3;2htNfhy zqEa+J;9-XP;OdM5pTRs~5!3j(g;$p*KeBs{*tQ!fCC*@k4tF55(8U$_O9;naehr?R zEDmGYE$mQos~1@w@xrdp)W)9+9Qyd`SIP{w>iCFPJAHy(VIMSqihq|MK=Hr&O1$Ui zfj6=^e;oFBndcT`Vy4g z>N~?g1yDPAc1v7*_@yapt)cPuk0{;FPIX&b7O7j*R`)+>;$=`32E|q~2WqJ&SiXlz z;_jjCb!!r&hwF?^$tL3*qCq$+{{8cqZqXMnc%vkhzfse)ga9z{ z7bjzd>|J2eHPDkw)v5d(^B;|&%&JGmW{>=p6Z1cy^9}i$Dn-1$%r&ZEyM$s5G&hS+ zCRp|5$|iKJM7&!weVSC{fHoNpYmcX7Ll-MxwazHipfVfyQ7YWqh<`&HgqBsZT>OXyO9{p7BeoRhLi0sDi0Gy z9c2$k@xEj?l(hbAE>=$-q`Q=JTIpVB?;ui}!_=1Fss=BS;QASuW*tq3lCSu9`kV>B zTPfnlm>7S}(NQ@3n(*W>z=3AanT$y)J)XouZ0R485$!S2p`{u3@@Paw_h=dEsanm(&GO?1IVe3(i*(N!IWE{?-@LLj4MI z1XRLYOcTsan}1OdwrxBQIIV;>~@B-#lcq3|sYT;7u;49vvm?chbZXKVkr@L{yn11pI(H zC;d({Ax2iX-7rMOv`f34^R?V9NOV!2F~QB`n_cHGRAYBe7!nJ6Ij&gO{=GWCy%Fz#4S7}q(T|{ZeM&OTN;lBEo;lHH`dsHSBO3U

bAeq$d1AOD_Yo{sEMhEzi+=K)pBaRMH$Ai5^(9NF;)(ghjO&WoRn zl8gKkO}6~hS!yvvwzK)nBh9kpH(+L0I;gd3Zf^azK)q%WNsNjh-2RUypTP#C2`G@| ztkfODz`4o;;QMt=fQ>sf5U#k=ktMt6kdM~HPeUd-53LjKg)FG#r_SnXrRO|9lQhOV zQis&%B$|WIMI#6962Zrw%sGJ8h^b$LQoTA`VG00GrRZ&-$^3w2JwqT`gRMYM9KrMJ z5{o|0y9WcaP3w>uj)q^()e86a-`d~5U({~bFM_cE4JCXmQ!LcivOfROTnGhMnmE}B z!MV_p-$(aBNL(PJJor4h7wXDWc2Rnk2%%p3KmiHQhV~>BSNsb~#0CfK0Qh~zj)Fqk zv&26j?k|l*`rVZ5sVCG}iuat%%}t*#qPaFD7T@~vdY?|HsSZp^Vh5bKPyQDc`#=5d zNE|V!pF}{)Eaq+X9h`%ll`qcr%cm0X)tt^?&ufXPc{pLz5DvK0!;}4dYT?#Bne)Qc zEt7@gPw}>OBx6DuLymYhewtTE|64f?{DBr z>hqR)OlcVT;igvPy`<`mi6UdRhk6;ZS5&9{Jwr-7c1P<+4Q+3Zod`s;E?s{V7vOFn zS3lRDK|(FJCsWcjrCm^*?L0ww+*KjQj2lY1{P$8S&0uxbx876@G^no(uB^VL6YN)6 zf%4V4CoxhdAAZNPDPv8BSM&hW8j0j=gh}yt982NvcWVBA{6Wvj;&&uX86@))B^&9E zI%`9W=msZ+?+C+|9d~=9`;0ro;{+j|kF~Bpl!sN*GqgOfNY^DkUQ0SdQtO4hZf5CY zxnSpdV?Ea~h;zK9qk0G~P;I>n2~*ti|8ng~=wtKz3&`mvTWFbS{2#+lI~8M)K)XliS6a-Po+^1zcJv)PKIjHeCj$M>N^7)Jw4H1ur#sD|9U8va*v?l9`!Sd3_ziFZ9UMkj?RL`Q+MGK*y`i0z*s4cBsi#|LwGH zf_Fb}j$w+^s*(BxM)EMbTeKC(ZH0DOJP7y!4Comi2`xNUuiCvTZdJ3DpG;gfRtv=QM;M?eR zC)p?=OJrz#cM-r0)o}(9`2v#55@mFIUgFP0HU zx+<1!9|F?$-k;m!!XbBSbDy(Ld#I)~Wv@TJOK3gEAAcWDM;VQJU|M)NuL;jWh$RmA zeI{!~_3PmpUe_)#Y+k*SIhp;MZs3=r1tg}(>*D1Fk;+xtOShjUP9OJM;#&~z_}AHj ziJ&WhUbQK}OUCLbmnkoSiIBf~Eb>r8p)jS-;BF~nZzcV-gLWAF{W7E3>9_gDr;HvV zS=zYwEE}h6n=~7aY)8#8QgH{0C#I|K%LY{16I^{!hTI13pIU5dD)7{jGQ>UHYM5|H zNL*2UE9y>bJWEDjU`&dviDF?{QR`p=y_uQI`ZT|9ij5Z`wLL}J&hq>E^w($a+5M%@ z>Ff(A9lx?nNeg-^qL}P_v1`cB7XAj~4i!Qu{;Z3GyqV_=mtJ5yuNr8zZJ5E}X$pBR zS=SgRaeYO|s<}z>(s^l-YLpYsxr^nsRc3g3T_fW0v}{!V3)w@4kOU)oyUB-lG8T)c za}gVImnhrM6YmW3zES_(a8`!H=fd9BVJNY1wRl%weM()E&|1H#|wLuw&_#Z#>s*ddg((h(Z zd#?K((-|+ZL}r^OYN+G-Nc!zbcDTLQ8kZ_ItSWYyFe#$eovev>QR|8KdA-XO6-L~0 zIwBUidr+tPml+bmR%}vv(~R|>qlFeM5`yklkXxW~P6<6STanh@(H@~YHaPj-3cVgG z&~?MQVbM2aORAeK_MU`PQPL<`nt#Qu-wf{M-K|PD`4$hli0@S=6?Q&~EjG%ND0kB- z>(t%PNR1!WmqE7<+m8 zhE%NAi-t@Q#SZwKpTd2i2q|uK1)>HYp*yZAjL=Rq!%M_Tc0(@ChL)0G(GQwjeAT~x zOjLJyW_h*u8_JXI9&7R-KaKJ`s8QH&3}lg#7RI|F#EVNWwcJtzy^>EG;uU{e-72N0 zSv~#DA;tj7k#!uqNMw11e16jn#%HxPuaOF8-3&H*ZN{X^FxR8W_< z%FfJn=R#5JpQJa^jt3h{)i=hi(s?@72H!t_>jww-37?zOFH=33k&hC2RFe4GH^1VxG`hPLR3Sw%km{0=W6l$_p3`^9Hs9Kr z32yWAJPOc+aavDLq#OE$`cd0~m;x(L(hh_ZOo(jD$Twj!%_O;qC3lIIM$x@*Qw#!^ z)d$4lQ$Ftw-!S*HqXW3+G9?<+Q5FG)t&Xa~jlx+Cmx9oho_n5t9QEANj6d4fSh&{a zqmyqriVFXUhv=%d7d=_gwRoe)ie;#Do`S(TsFs>H$xnV^T%ZY5dHh6qnNZORlhdAM zQv$P@WS-O8kKn6Y>XV*Gr=rK>0901)UnU1Uug6@}d5QriZ`FzV1?vvCPU!dcXF51u8tPx=P8isjfSKG7NKZI=F@E&*vU&V$gRE z?6hDb++@(u>J17{wP>u*C-My(rEAUwdfsHcZ_AfdvsI%QTB7&)&20{w1HT*r0n~;+ z24GCrB%;crKB23`wq+(CP>$x*fO-{yDe9A(EN^6;W49K*`+FydX=`0M^9n=XlJ;u8 zL^*frK;U9*$I~h_TLMZt_Q==2k#4+y%*sB-0q4bWEPV{zHAM$K>wp*%_f83@i7J0Pdx#o;0ot11ZTWJRJHCw z)nsCo7J;ox-VI`U{+!-ou9=|yjNuPYXQ#GoE*8Gl(V&ovfhHZI&uP%SU#Gp}%$km7 zwNTF*zn#6uUY_G_AQ@6&4ygWDpjyIJOwb+2ctVJhnek-lr5Uq!yl(c>N#77t!%>qZbJ;9n2dfNACk=Cozh*)8*PyS6N5 zEY;KYG_Ibj+I$jExL;g)yQ1#%J(P{4jqLn|;%OHr>i#jnL0@Y~;|T6wzj>Afp-W-b ztDzhBUx}D)59v|@J9*I_06Rw9shv_mD?*aYe{RlTenBI~*ZYg#$)EnS;23&|bQ#&% z0I6BnS*e)W))cOr(7f!<^-V}&Su%d}=hY3JLbuN=l&ttTtm&0UvnPz*HJT6=*vEKNU;gaB;dCS@EB3be|)i*sqc@V#= z`es>I_=5%TxOf=O#UzG10DIo@6n{98`%IDp<%8ET-{*;hx|=fSB*m?_*Q>k<-YrKa zY1Qr7g%b<7uM|&84svy_mG&Rq4S+%6&q-j7)Csbq#T-yz;b*DEb3O%ocs&>MiaUUZ-xved@EnyGu?Uq8LqYs_~WeqpO!1kcsr{iP>Xtfdu` z`_=Tp^#K2CG~R|`6M?i;CIMV!PKk72*^2WUl^l(IuJonfz5{nxh^u*vpb>(_Yjb2C zRRv_=pr3q)GY)-;)C!|_`mwJV`JIA%Vum;~@U=1jG_w@bOZ=7qw zE8cwt6ub44BSJeRA5_<-ZWEs1Cwn~g^SP*OKZ`Mm*ZEzV-pdI`d|ubjzSe)!#=OuR zbjo(>AnlnzIg*keT&WMPF7pZYPpkz4%bnQxsW;sW!A$HX4TY~{Z_xD+Lg%HG`} zSAK$SPlflatQ8hERg9`l$%|7|k&-udo4`@wzi)!cs)X0k&#x~#^=&@$m-*(^XTXl+ zJCy&eMkr{c#6jIbb2h@Lp)O<%S6L|DT(3KQ#8q9;>BE=z?T5kTGdOzQugjSG6=3uJ z^mIhm58-DF3_MV|8WKB1>|aVbi}CWxeDNr9@u%%Acdxj;#0=zAndI;Ul>>hBhtNxv ziE*)jfxckXtHEuBLeV?pjV&T2_Z~Z2;axv9Io6l~LDN^V0x{(y=)n%iC)RWa;I^{q z-jv(_@+Ez4$M&#)_Vp0bqf36F_4S%aJBT9Tfb;4U--W_4kz8S_IF&*;{!ypucfq!H zZ4|4;YK!Hr20dGx#vPo&4OBaCSU)8l>O{FnxI@VUCZsdnX|Oq$dG>0q7sesNsmtD$ zxvSnZyw3g}G&I9ta;JTBVtLuggRWXiQ(7VZ9|Bi$yxar@NQ+z=(h*6?7C;d9oGq`96n{ zhupnhe~vXXmqdqjEiuq3;{zKr#`c55IGOnGT%wT=U4b*!p<*?vIKo7KI&uYne0jBH z!BsyzG5pyJb$g6vI|%um1zrYat$7!TGh(Vtxfv&zsGb_V%`Au9+sK8we$4YoD_OU& zrihjDGETiTo+kb|yC<0tiJ0|cO+*UrZK12gd}1Aq5X zIlB&$W3k7@D`@OPJe)9#Z;Q{nh7X8Egp?azjyA5z&`oF8yQFPBqkCFZp`*uzUbF5b zeUXKz{2Y!w{|s;5x1sa-f>Xn2t16-&+EXTz$z$X1z&r8*{R8^joqB!~r$(U7mNn?o z|EuGX3ChUgF9lmv zTr|P~R-OhtfGcP~R_(p!i%uZ>IRSaWMMUWH7}=at>n?nO>J#Hh(~g$Jm3<=cYcIEx!|ht}O!bRKNWaJ->7B z!a2oNtn4S7aJ?}}NLB?4sJ?U-YRctUymp82YFzg$-g|VV>ZenleM{70{^~8}*~QeL zUcVd3Zv8v&DldQCUizc$mnj1hAdm4DNaHV4TTw$pejsg#$t|dhd8!Rq@t5a}(VT9L zBON2_-Be}+V!kV$pL!u{*+5-2Q3scpRP2)4?0im(t?`9%X-;afpz2B$I=t-YHUY5p z5iSTV(nHV{t4K^(Ld9Ac<+!dGY8_rLSMgLe&loiCJFsN?>N}U(WGZ#FbwzTJd>x>; zOxID_K;-dwrha5D*t@nHVgy@c(^*MrHvIMVz!|N0B zizYzdXSpcpe#ld9?6-@z6~2H0MS6?;C-AMW!K+yaF zeG0tyyD^Liwa^kbtLNboDQNlFqolNAta1U$^D5E`Y0|l%6P0RAZks(aQ3gRO&qz ztD?RHx>6)j641fOnV~MWO(=Wo758#|xA^)SZ|{2^3M%K}hTl2zc3$?5zcpI{%fj$r z7DuHm>#i3Qk@(c*iSJ3f<*m0r#uRB$$9vd?;azN0L7`Davy_u`X2n4t8Ts*ji@y!+ z31vryZWx9Dr-t4UqPle#j;QL0gLH)kuk*5)O@RZ#XTh>V>kd!?aXEVP zTtx*OOld1dan8@m8JLv1|Jn=*@19C^=|UlMHmBQP;(`8Q9A^igI@#|Im*1P zkwqp=9`b`%5y(v5-Y4?`|5(2TYUb=@7YcvgoMIzSwLnqu=wGPcaDsI-L7MVbgB#Z_ z=w{3&`PyTCcFWkE@W_8eIZY|Z8ueuE(y0Jd8iBmp(qD#_LgxHlT(G3ht_=2K<+qgw zsiWWh44R0&fN<9f>%Z~m71XUwZAH8&eo}}NKOWhR3h_@#&9F+S4${8PqO&i2H}+ye z-*r9Q{;^M?+oaXGGWSpiFO^*E+mf5DxF&t!l)H|8PY1sUv+4=QQu{R}$+Eb;J`}K6 z;l?|~6@wyuK$lJ;@oG%CE_cz4J?qZZ(fFo z$YlIZpwBn7AD+z%zls&;)GHWVJ&CD<%n4D2 zyG>>OLabSor?KADyo!^LM0k}>b+1W$kt-Z?Y7&02|53U?t+JCxGa@w%NvXmtmP2c~ z$(+SK&9H7FmO51Eb`-;z6wxXRzieydIh8alo^23iLi_Yh!oA(|h0j9&;7C*^L0n!| zsWk9{G=Ec9CcB_)WgMM9CN7#tJD}1*hgN5KxH+;Rzw;EV;5VWY>C9$4hM5d;cP=;N zjs0WW2CsK(kP0rzJ-%~|_iopv4v@zP{IKeIuN&I^?nh$#&WqUG&s;NRy?-vbsVpmS z5)BNwsgb^qCbK0`4MXg}m48|RiWiyf4fHfE^)564vvVpGBr1Q+hcP%^=&=emgxZ&r zL=me&$9;pY7Iz^hn^E~#aCWVlTazDBj4MISg><{;!oTtEKniuC44ka`KY+&plu0;t zg5Ne^S^kZ^!^e%k(B0)*{y8nlg^FF}=V_jN?f5KA_w3}~^jA;mML_+90{fK1{wZG= z)8_tG!04;S!mE9w*J+!ki)ouW_FzLOtPjb^eW9Kk%d`xCqbZVcJD_q-X=WyiK?3D* zEox5br^9^~pVS~RRO_N9ccN#kpBKZ?W2_(fI~HPu-rYjz@p~y(w<;D#Bil+sg2Y=6 zH`=LDa4bFPZr}`|n3xo_JR%erPU^WaJ|(QLzO4Ri_|AjAt0}sQFW%6s<`t%O|I|V1 zniO4l@OTrn-s!t!euSb*O>^~|)A>Dj=PQ8b$ch(bi0sq=a#|QG2s9`=rD&4e)`}hO zW=~6F#QCC+>I2`#lm~sZh))1s82{T0^R75Y;m-sK>-jI!wtqPwni06H# zJJBemUmQvjf5~f)cYe)zTnu@UAY;cD6vd}nb~XTBn{l*n@c1aizhR@5q1W_9f6(v%EBLt3y(^#V{uA*2VOp2joso!}tEbj;oEqh4)zAt0g!bN_%d+4YO%7j{!%~T8}nYJFU7C+BERo! z!nha>9ZZ+8jNCdoio)GP&BT%j9F&UMn_&he++8L>*q*K9&<1EtHuyAcVYvf z(sH-_(Ge@pSqrk3*52gvo5wc0W36}E_i8o{ev=ZJmz$gXMXm*pw(+VfQ@@{7&bv2c zQLa8TOw`F!@pW{3nhxB+i_Q0gDCaqe3?v|M>g>kUs_nRgw~vEZx8&jnjP8dzsQh?8 zN0USZ67Ss_r!_tkJ zsv_Sf>Mzfze-PNT`i?Cf5I$6G2!XX|8hAT3Oa3vt1_9uucr zBI5_+4JkC2N;cF!sGS;_Y>Kp5sK zYOScLGJm^d%Gx*HA`Z>m_KUCY$Kgu!u*$SJ^;> zZ~w2J(^}z*Xo26PK8ynttPPr41;}|#ev|1;O*;fUQmFL;QxyA_Fc1;QPXDPAl_6r@s2@fW>S+L&$ zqJOs)#yeVBHv(kiy+=Zq{<0Nn^5Hd7bb{p0`F94!rYK$Pur#?!v##yVLS>Tq=g+;Q zOdMG;PP|c6rji*U3;56`G~SnF(^5@kAcq*jw%?s893vu@#;$fF`5j4 z`zAO{T*Z#tNvD$MLyRtJh34EqF79uKNsQGf3VHU}LbYRP;-q(F3>COjHd@GMaQ4JSiao}J#B3kll|u)M`9iB%MefmN%Jy{SEry61HyHtM)012H%M zI2E`qJg}^kO((>WbO&v$(>BFqw)1)-eqEq;XbOomPi|QvUENEtZu=6 zvwCYy4cRKGY7o;W?pEHTJzuBMT^OQ_}J zc`rU5Y@4Q$s6@eMFvZR{)g+ z0SyJ8a@Q9Fg1K!6^)BBvl<2#j#BhELur23drKh7y8G~K{emOP3SdW7o6txYW12cmA zNw)23c6vr#p)TMdT;hfq`0UE6%Xhy*zUkOtUzjFco(0tQYBP7c zK;?_2P>?sGTI1_d%ERG9rVadoea!{O6_N=R)mFqY!HG1W2+oS$HN76$CWV{@7A*f&H6V&o7$`Vh1syJhm zBwOAmp>Jv9Y1Q(R%H$d=H+J;bD1CBI zhN7`%$ihf`cVCl;g62$*U6ZL$Bbe3dOKGvIWt0S->!Pb>B2a;=v zA7#T6o?kos*vLY}hDBKXjk+B^`3T^tRNZwQW;?#tSatDkD%>KndPv4V_!%SLwG^L- zCU31hO};s61OlJ%YhGkDe-z<~BdniC)+x*COi%yiEwKC{R^Xb-%NqV;@I3#kUB1?# z#gUFp4M*HizZEs6B(RL+htMk3av`(}ql`VDX$4~iQm@W)+3x==~*8EWnp1YeV zU|H?;0fnwtQwWFYwxG&xc?S777h9^dW*fG>RvHl>A5y|9DkzW(r6Qe%dDcRmdjU1= zj*kx)E+wU|)y6JEp&^Jb?<(Z(-X9^riP1g6#H z7F!eE5)s9A3(~~h6Zfxm{7gC(SX&{BWy+YA7j36h^IMapYSS+_rFAK95%)FJSD4^7 ztz)$dT-$uF%>g0SB`sPW^T}%FW9O$CV-e9Au4kRTf(o6(baK}BOiVH^pfqZaR?#k> z_hEXR6?*Do1tD{GS0pP%^exRZr6zUeTKw97HiXuVw*)=MkZ!wZ$`H*R7SYiYC&q%p~S41#bld{mBOG)KgcLnYf(e-o6;0 z&&>v6-|omf3z3KtF0xQCgXLU!QHYWk)Gb2wJt zO1dS#*;BID&=t=n|J*l+38If%^3ms-QL$0PUHw1z29joMk#ZS~T# z$2E0tu5Hb^5BvcKb+qLck^?xC*Gw2k9t`nL-m7W2)gUC?Dz4C|HkV*8FDZ6Dlj#!U zM%|Mmxb#AL)d26)iYV$y1T`mtOOGzRqmT%^t{)a*ba}qq~EP)*G}S=gd!?| zD{5t2F%e~!WHX8N%ut_MH7xUygkD_?VN9m)^ZFimY|gAPB2Y!FpA~nV6CY5m_}OrI z!Mg9ewX2)IeT{fz{>~LkeOvbsbl1oP@airE9yEvDtKqRy>N%fxJPWg3MWc8gx2CH9 zYu?tEd`Yu+i~XUUAC5$5CmP`0d%ayWIf?nWy`JgS`=$q2$)1dW1{b{t_KCVLbXlH{ zZ`A%TPq+W~_d`3o+r?G}US2XUtUIFg9aF*!-Od@X2huQm2b*VxeS_8?buG+jGI_%z zsWJ`tNOZIu*W`od+S*2Iiz1KYIOUXbZ`E@z;(wnt)-}0x)9Qc1`bxVdA`FMzX~X51 z;U|;)8a33HZn6xJ{0o##?q*Oues&6(sW_<@rgOgxdfqKj+s;Kc{t>2vxc(DiyA-N> z)4ID|v?l4n7~DZur#W1QF_D(0gH@}O=Epe-hpAzJZ56IPCa7d9Afwqor});9c|d?k z`Bm#Xv6ia=_M8tNvs)z7oXMtX(6I;8bfD2;B1BWaJ(lfhxwiZP%=3a`n2w*z3_VNC z!&pQiYtUjucJ$I@FPwq=B^)pt}hS?PloA&#QG`6hNsk>lb^eerjfY1s8? zPT%F&dyV2EtKnCc+Ec*i=(f4SgmJ@dbkn{R?eRDrAEA4z&rV;}I>UPYpV90CIz8Z5 zc1nk{C+x>UEj%3K2#L&`S0zTGt%>ekCmty}K1gMgdnwA5qVb#wmQDV&ujXH>2TMbi z{v;hBW2rR%Am8@Jp)74rb7*_nCp(eqin&^RdDUI;AQctgvz{LjHtYSLh9qG32k%{& zmIMdk4#gSnwK5_Ys18PCXR#Wfx6P*AjN@r*zisjJ3f|PKpz(if27MKFiHKZIingE7 zP^xsHii|pmJG70-JG3?=Cw|c9mGVmYaZ~}G4_cg(4}>SkNJw>N99ao`zYwu&Vq_y3EuKY@nwjsJ&nvM&*ZtfQ=1BTHqO zBwLbF2r*>|$(C&}W-K9l_M!+O>tq=_V^`MfjCF?W!weZyGv@Q${ht5t{Ql>E{^xwp zbDrZ2hvPnT&2?Y*b-kC@aysw%b`;BV`>Vq6a3|9Pld-#WdtZ?%^{2JE!prdCG?+7l zR{?QWqSo`yuHTcz9>E6gx>24sN2}W*S8rcDXQ;>AVjc+=Zj%RiU<%_@&(%+kqM_Y? ze|suMQy;duzb?lMpQ|qK;Yz&0P)rv9M7~t_(>k~yd=zMUQO~cUg`l-#w~651(7s8k z=5xii3EeU+GZDG??}_F2(hZwm7j1~vC4dOPRF2`V0B!wH+IdA`8vnCe+BgsmAcny& z4+DcgY&QmFAMK6e^EEV%{`WloTc!W!#Uk-P%0T|M{-4QqeH);|M_BOglKIbnDszYO zRWw;z=`4WP4lBU@W9@-}yrioefCZJ3-GP$s+^mAY@sAvjH6pUfr@C2mYCn+yRLR-uV)yFg$Q=KPnxVTSFVd^t6US$g`4%2qcmK%HSRut@MZomR(a!hy zg0dz0%I?$er@d@UyF|6jps63D5R$I-Yv!uGw}s`>O~*~hJ5mDRr~#e!ECyT^^S!e;15;7r7;Olzz0SI4^#nU0H5d-PGke!aCy zM*1;~uIZ2V28I#m)vQU+EB;v`aA@m$ukV1)!sx^J@f0O(9e26?;~vA{^`iqz|NOaG zEOS)A<{aSj=IyF6(cvs6Sex`MB(XAh08}hCd?RHI-rpZ~e_t!q&7~>l5lSMCqc#5) zi*)msp7NS~h?1<(IJ%{@&*JO@VurCD$fm~7A?NPe+$r~Z9q_G~h27jJRvs$|or z4p;4uTzs!@mq*1P|CKAc5bgJ82*~I^BLJmBw*w4q1$#d#Y!hmZ6{Ccr|IvhM#6j*d zqOeRhRz{bn=E*|ntGg7R!s70~Deq|#+XOwSscjLr2~!ECI}e-R=g~1VtDiQE8`W_+ z0bE)snLYpfZ>4*Jz{8BC{;Ul7)t*91ZkrR%f7q<+znXf|*6{qC&WseY@M~ecr2HY6 z##I6=Rk$rsEu=4@hW>o@5N5KcK&-)=je}hNh~w?scf-S zz%%gMzw*GKismzi5j@AI42Z5Wz#rF*jHGCJtS}r8&7?b!U*pTR3XO)$zKqF?CM>>H z5{XLhUhFN3?mT^2_|eJLXM>$84@^W4di@IwSZd$AY5>>uSGy+`@n!NAya=A()+DTf zQJwsI>bd`a6_BmMYPrE~FDziScJBX6L-+s0TmMH__ka2u{aO6C_S&5Dk~M$6|LD-b zY^Nrc?HM`E&2EhKgX!m}N@Of9iEFbLGmwC)vXE>eb0eHNF}bBHCft7;ni@@Pb*cO;plH~@d7qsT_uF!JRTIYR zmy>KN?BzS~cQNxGev?Cf0&mhi?R9fQ{Lzc!MDSi2(RP+}V@4gx@k40WL132mz;>d| zN=CkPS9M|D(Bb+o`Y9EtE2nCo@Jnpd*iVcE*_%W<>PaKsBN2T60I42d$Re{dk$)S5JO4ebnGZP45#Qg)Ro6V_Qf74)YxzP+S{+#GBrc$OodrY zqNidE9>u6gcLsh;ebRC9w5rXvZn=t20d(kCdYw)to7sR*@ty8yAK4i{hmt3ue2l>d27^u>$Ugth98No?pY z`c;-ZO5ARKUdrNDruX%SjlUh`!T5IA~rm&nwIjP}yp z%H#LuE&Ne-d)K#NZ{cE4x1?Sas?kd(O(U|Tp&m_^RgRgEFGGzm^ycX1yQ;*5S+N#P z)swk=7}M

hJtDj@MgVjY|AxNw`pc*S{t;;R%OpoGH3H%MV5C9lBp_ zGBt8%1bg@7U))*YLVe$__q?q>wYQ&Rn0|jsBE_`kLzm>zidnAB7eTDm6}G4JN~;FY z|LAt5Eu;D-vUoE}$|xP<%`ej)!YT|DHSP{>ql8}uB3|GsEOM%(#5{v7Y&`;kCLujY z{UxvmGC5Ce_BSYgg)3lA0o#&qQ4`xu%CRi&c_~z!`fhYTocTya$gr~KWrd(XHGfCI zq_?7WFz(5#J3%3t>qNPR56%udYY$-SMp}1F_;)W|kGME=Ehace!&^mnoT%Jw{x(mN z&=9)@p(9sxn-RJ8m*sWf$BJpDqPCo8-n&|>ypLdE{R&dJTZSpxqR9Jsx?FfAG3-_j zLt)xDeYCh=?w55x`MAI**A!nHZ(=hjclW{TjKcIs9tWKk5cG+&n^WX;?CB4 z8pNxaXR5B;0z#zJESLJL7opg1yG!=Rp1_wkO;~?}_~gUv*PW>)nwT+==l$mLL8c&9 z+rZ55%hXWdP;vsGOFalq&*QWDkS2|~#qAkEidC7*g{`Bmb0V#L4+6QK%8PT_xl`EN z1ZeD60P#=4Sf_lLDamg_jr$yw)?*#%Uf=$_(9ZMrfOFxKBgs^{g~U6Kcf2#Ot>?H0 zk>tzSF^K2rM9>8||8}D;JCVC5A7hH85=Nw|8!PNl=s0ydD9zo;M$#>+vfSl~#_gRQ zI$kEpzeqgL<74_pZYC-NB=T57_UcMv2R#+oI=X+z)U?miq<4th|IsaE|1?=2<5avP zpDG(*`B?jo{rZE!B9Q)R9Qvg>S$30p0lB);4@!VA!vqN6dt}Qpo0lPZ#V)~|jUD11 zrf>HxE|;mLnPeqi&StOqk_OwqWjk_Oj95*F@PS|ClVt&4v3Onn(|m0PpbUJ2To&eA zshe5-bip9FVEO5=m(;@My9aT34+QxQ+~)N?pIxQjMV|qb9CDF7h%RYuK&QRWfp5Z@ z98I7+N$FADw)G!w39JYOXOldlJx48gMdv#MWxj`19Vy)eI<7L-ePau2YmJF;Ve%19 zy|i1{Jc_2(aI*?ujA|Vych9m|Xq(w9L;JXMXIv0?SN7Q+YLU zED^3`9l|Urn4E+=3s>~JOE`!fzCDOlgcY4x8an^I#OA`H^j>&$(O~#Ru>=}h1yP2y zMVLVn8nNcRDUpEOH5w@KGtLiu+3irswrp%_X{l>yaI5My;TyV=`QZ}hPBrVI&xh}H z#9ec;yfQwUa-TK}5dq{Bc?jU}05c=(SRzXPB5HT8?X7>65{4A&mgTMCnPz?|>FO;> zJj^KEMg#F`jI1yVfT_GZkU*ad8=IiZLRD)f=%!y-mL{h3Cf(N()jg!@aSVBLsK_=2 zN3u>!2DqctNukUTR^o)#g0+k>%G@I_rF zyL$m)R)af6#Jr9jDH5oGy~yW!>XGfh z)<)(Xe_3=t=G9N;j4fL&f&BY>>|G+ z1TBZe=hvRy$iXVGs@FHhDL=N>h2dzDvTPmZuFG}640_EYXa`NG4KTAa&$i0CZhOP2rY7V2$oYFe-vC_-(p}uG;)L00 zP-7{T_hc=*A5E4sO-WLd|)o*QWUYuH5@* zpdBZCFZ#hZjZx%zaUZFq9==Y*#0n;5Z8@K!NE;QqPEIPA3}zbB~W+`%E_)Xy(5FX1;MC2|{BpC4kq8PXH0ngVmt$de#>;9Y3|@5vBc@d%g=# z%k3Q`UzG%Jrft#}S^K0Ge!oy{zT=9|rD&JpohUL?ee$c>hjTyH%$RpgBe!Q?+m1;( z>&tT$TbU)^S`YV)8li)W!{@muJ znmai0`+GrS-5QQ*z5vKuv6}j`TO1#+Hv$o|n?)AzMv$hiFfX zaiL}2DoQj@Kf4)$rij*@$2&=NDR$fnH?#q{i(5Cq7eNHWv3P(V2BDt6MtVBwuPAfJ z8~B~B9$L^ws73~OOO+IF=)Y7_9Bxf&{gyUyDaKd0!dNp|mA-<)0(?kEnp_|7B(f3E zohb=4MY15#VgSb!wdgK(h3rxxbzGh6Uo#@!_~~7=Wi~|2!c6yeqrEmEZ!a~YT66rfB9YLh!*bHG>?jd3$|vieqW0*oj6cU z8KtQ|dVfzb7s@^^2zJqF> zw8LK;x$J1vlGO2FRr5y|i{K6%7;%)f_p#YE8ru!x{Z0LJlUUnE;%JppzVz28PX>Hi zbl*E(%e>U~85^`3)p%W^Dohc$pI!jlQye}B;E&qnvFMaG@M+YVsW(35+I>m@YGy?j z7nE@F?K@F)a0d#%HhKe@28drF$daW4XjViQ6HN=oF;jmXCP=*2pLZqQYC=(JM)8H| z!s8TUC&3(yjfAlhG@!Fl~0DadA z^0m&E^^lmVqR)&igHEp<#pRjOVc$XYNAI|2AOuSUSx%-e{|%@?peKL6krYNai48E1 z7t^h?H}o`m%7phP+mx`E7i4%nZpg8AUF75QNgNs2g!FQgCGzp=#37O#AtmCs_q;iF zHaq|sTNl$Y`^b9Ii71owD{Rp8{zQ1q<>nw07t9lx?4(wfrF_73Z|>1rKfu0X;sG?E zEa77<%-L(+=b*}FLibAnto7_t zB^?g%PBg&)5-xUyP#V=H?7rq5$1m?x=zetpRTA~3oZr1vCHV9hLb8ZxZBtE*WL zli~QT_$wb}P2~y2mEr*i5I`!(hY35CZigU%hbQuz;1>d|IFvDK7I*K@%<}8HPG_l0 zayHajhAYoMhO^MVg9$eI3Cm|s+dDv15E;E@9uDTO2gl2Pn+K|joX2ku`lF$gnd{PS z-|hRR`7+=6%GDZo*}POHhqwaP2FAc(Z*|;&(UX-6sPZ02`kh8kmr9QZX6)N7VO3ob zQPjQK5&0oHmKSu^T0W-IC;znjNC3ID3_cB~Zz2Zm2@}j$mt(Q>&TTVo_U=*{v+ouM zJmu|l5`709G&D`Q%nxpt9K7ePasYw=Al&#D@r(h%i{%O?7iXQ&rhJ;dn*Vlo(hR#~ zM7cY$MlldN{>3sFZ|g?&B6CBe08zGgL_V zLUD^bQKzG{_-()i<5B~qzR#LGe*{xM{J0d(uK&-g?-`gZ3F-6HA_bq;(LmHY<4y!~ zs?zSPu24t%LsWb6!`%L4S37rSXM^BN3e|jXWf;WQx-Xw5qP>xPz;IQ7fJN8)jqZg; z!a&T1&9j-3Sv>E7eT2GHTk^!>2Wl;B@4MtXL&k@U*IzTxg>`B6t;B->faq%6O5G@m zP}&P(gDF>OM#2=`gUhJ5(fmU8Nk!W#ULRcCc^J#PSgp7OgRmxaAQ@nZ08X3s9ZKXQ z8oTyN-DaOMFHDeJ>{01%v2)WqL01IAQNikx%y1)R85rs9y>pUau3g4*0B|{5bRL$l2Qu8ET z&RB9juArko8KCk>tZDX+%}_@+q9`6*t4h!H7v0FRM)X- z$$ti??t0xc`i2+VsntxqhdHm?Nl0LU_t{@bRAoT8>ATR4wE(&AorK3_;!#sVG#GmI z9r~LaQIpC)X-9Z!`;%zi15ieKg{oUi@74v+bo<`ydBGR)$K}XE@Cj$qjMt4(W0nIT zR@+jqK+wDSlCwhPkvs#Xoh_9!z89-DT6vHk#|soJIdkL7B*Sn0*oC}B1JU{%ur7d= z|MUO@SsNHo#+(r~?Y9bSX$yG~SBP-8x3Qb=lX<_@S^)jU#wpKk7!fY@#g%hFol*=* z&O3LSfbc+)C6Iu-<+UfLc>w=AXicCyC0<~~ODJr#Ktw?3Ij^atN=w`L%^w%D9xq24 zFKJ|9D3-T$(@#riLS#Q0>%X(6hK$1H&!60>oAj9>vH%v>C-`KZBH11X!D!(I0H+cxa(>p7V6a^CW$ww)#c5Q+S1Yje zy}G*cr`I!-EFUu`Vq*_LC8y;beDJCK#seyk9WFK+h9Io1QlDyhy?1q^*Sq8~2N$CC*v|GUlO?!f2`v3`FlC(GSga4(>*S&*8IyP3tr#ezuM6mZ!LcQcPy2UgT}T(tmp=zT=z~Ww`gO! zXMa-lD`1xhr};5n+7g^U=NB-Th5BXN+tpi~(pqH&mxN!-70`tn(-9$a$`8oRUlCnH zIoVX@vzk3ELA@Rb<{_p(KTSn74%NOzJkJyD{iyxilkOnL0;c`4zu}r3^}*((t%~VgZt&mxd3_e#iu)mizYakDamgQsUtOGSrStCWNtrY z)T?bU!Rlavf%J!H-Iu}#AJG*d`?>-J0EI(2r5eE;fG^4OImrG?$4u(l+on5*(|6)q z=f6z@^TMcinuqZ4CQAdjTO~LfG#1Mh?MtY$Bw8*vmqX_RDuNEr%@45G^Hg>%IeU1( zuB3$gNoh9ZTG1u@Qg~_e2!ctw-Lk?lMC-0g^32LS`MY$hnf zu4YNKHO9L=WtJ>^-R#+#0_0CrkrP1j-<7H{yO#n~(?K+oO;QH1kz^o{|Ixj(UI5@! z4T!I&Cx26}omwA&TaO$&fS5lLK;U-ippazgl|4<5RrCdW+eK)d!}PwkQI&y*Os=rIKEPS&POghAaR~ZV@8HG;K=;qKo2HBhEmARGejAxtR>-e2pWY4 zc~Gt-?0MF6*V8CN6yJvVP3j+Br(b8 zh4ZMYmIVXPlHm`AL4O*b8mqEiWLN1okD!f!8xQc&6hIqMxr2vlJ^@oEY#fmg<6d?q z$)f>5)84qyh49JIt~&O%WoO?Oe_b2d!PhHBG$6|{^-cycB36MSNxf#5PG}dghj_kU zSA0szU-71nL;szr)lf4H*^R+=Y085wm0QaJXVnpk@LZI{<;8K`rW12S&vfl<+ zO?fKXj?N73-a946yIYvB#m^Wm@HYSPjWlMd0d|``6(CupaRGd;o2J+>H2ZLB`m3r=F3C{P#e!s}GbkKuXz)cv0I-y=j0Q8^AO5sBx;2s?yx#m!Y zcPdfua6I+kba)&WRU_X5xwK?yX{9bF`a?Y54EWq{fY1G)Lkm8_uqxAK&PMj`YRSf5 zA;P=ok8N_Ua3vIuT;jPJ!TILfQQOM0R_2}X8mr|qWJu@OJcKj?T(1NF3AnG;XLLJT=Uw5OmSGe3&K$l~8GgpR9MN?b@eB!^shjcO z7k~hW1zkkSzQO_}bKM(uaC&;cM@$M{g&YQ*_ zKwRkp6CR>Yvk~s6pe{TPea^kA92(?6z|`vbHn1saH!|YgFxOq#7anFA$G@XYA-65z zPiTEBHQe~K6iYxfe-HdJ1tZ)QBGXMlFmInNAtQRS8h zvsI!kiX@J2hW{_3*gvuu@VWu|pEzfQXgBUZ;xr8)RcAV(rT>5cb>S```COkx9Z-b4 zz`xT*JCv^iF!p?)o9nOxk8Olnj3T&v;}tq)D|hJ za~cHZ&T{B`|4H;&5qyR{Apw)2_^{|LKSwjW|M5@ zN4_o_cza(+AJck^i&s}TRJqPhx1Q#6RsMuRV+{ZftKM9a6xqyo4J>H$1;GpR^t_nG z49UM;b3LH`h9Ga9r@PUuhXQ0a@60tW@zP9}!%zRw1xro|*mZQIE>W`|ZIjI}m3>$% z;(+ff-q6j$?~ot}eY|pfKKCoQ67?!k=FPF$%6y0H#J+!SJ3917QydN!iuZ#I2KB8( z|8497#ltTX;O%UUGukR&$4VTebs?%UvUmTM_3LfvG5UD?9Y+o#$k&0QM{p?dGMnyA zGK-lXq4;j^<#ld>60$kl3Mg9`Xl(O*p zva}EcucUo0y|1=f)@38*vK1MhyuYu-VIz}c5OeXt4~b^X*U!#rp>*=Ufb&w$E8*){ zSO9Qv$I^6(GBLmB?^z3t6sP>XnL2es#}lcUd0jigCM(R`06I3ny#N7v8w~KXP^bRc zzq;*yrO>Z^BoF^&wdOY_Y#(5Ku5_YBjAw!?^J5T=u2h8-G4~J0YZ4tE`jy#ukz@ufnMu}OH9{a$L08a!4!9Aq{$X@GUL09 za>CQ-ew^^t5JE|N1mpmSm7H^=KZ)w9B3n(EwvMpxT!{ZOO+S{U$>(Lb{5kTLxU)0E zf*Vrn^aI*mhnrMPeho7!=n3+;U~OSD<{8j98fnnA=`O)!^TcntX-R-|LRg8@)*=eq zO#Tjgy=z4F=~ME4Z1C&l+4k&nFhHm+Fl$qUZ&a0uE)>PL0T(+hrz)4hM*&@`<0~(I zQnmaz%o4tF4#f#w&3*7`c({q)?_)f%)tf92j#+q78U ze(yx`9igAOD`kL z1va2Ws1r`P*8{@l(Zc<-smt<9@klDTe=!lIpf!QdpfE2PJ)c}hC`ZoFV0lD6FizX-u zgo?Fx!Af|TWxFgxOX24!)I~MVo4CUr%SgnIJ*c_vO%Ab$cy7m!K>Cm>yupo=%KP^FfhJi@V#N^n56)2+j zCaD0&nq&9mchiKSBgkv1I6}ev7JYRnb_{5VcIhe+j~inGMr8WLSf;%{IW-9VdKYuU zCYjHK^KKe+h_2%|{clUY^?XspD+DKP401tR8BojC4-+IB{cT-mBYCK_NxMizaHOf; zx-K#cAvUgwr4YEWS!K8?-*vmQN>}@@e{*RHykw!q7;f`EVwRz-lkp$&6k0WlZ^cp| z^STSTHTRoc<6h1xRqqdJQ>-l>UT{xzu*vRE+J%EeP{U?y2M3jxfJjCu%0FqI)OOCA zuL=#E3= z2LQ#k`~~G6blv^3Vt0X?@ERO9uSY`1kjVs=*ZlIPn^StH0_#P_G%gm#L~mfTxC1%& zs_l{JK+l2LRto@3nwi?1tgh4;dL7a5c8xJcRfUE9v8#L6bv60}HS0m3gV<&1AL`B9 zQ}}~0i@Ouj*BzYtL_Lz@gEiy+laqZ3_5pGKM_V4XKrJ@Z9n1;mCR_JmkZ)l2-axG6 zvN-e3{k7efpqT^VTG-aGE@`n^)%f}DEwN(*7@lxKV>iVr^HB{asmj$*L?4ZP3gyl( zuQ9WChv(j&Px*b^;FEXs@{KZS^3yb3syBd$b@h&JNl8%e(#{Hj_nzyd&%qfzD}T$z zAo=ll^-Smk78e7c@3l8gG2B9+xO*&#O0l9NZSsCnUr4iBuwD1_ENAOe2Q!~XjYs>S zLFm=bd3B4etWKz6$E23!Ua$L)4-1WuM_c zd!C{iC$#lG@8$`|j-e+}!WJn)M~PSeAeb@BbBxmf2XmIxNI1ar5wfj_ne_r8Tq%W1 zBOd;L(+%^U#9#as%w{DnYs2zr?k7w6ID`#`I7L^%uX;2nVjIYxT!9F>Q8%y-f!&qx zP^VpsAQi}}7$gUNvzWgzz!;{#ip02fJ179XQf?3wzO!rj^Y>Ye&=1%biot@7Z@v?L zw8tF(uP7y7xx6L<`~N^JxQvj z$IN$iiRzs>*&Co3Z6i=JJ+VZM6sNE5q?%9=tz!`h^K z*|K8lR8pGGr#fDGRpZLr(cfi$3eo*FjQ^9}3n4u66fh=7-~jt>GWS5vud!(F(wg?{ z`}HPkk1nJL!a;Ihiq6@Ia94%MbdD`>cZ2Jddy+hQ9YC6EG@h;bV-Ak5Sy~mZB(8tw zd>q5NVC=}neVdg|tjYko5(P-5iL&$~>cq;3cUSIC?d^+wn5S;N96 zNIlc~;N11aYa?_)zpbOF_NT219lY~fCrPUa5f9tIsscTv`hLE1El?^H_p7yDLfKeq zYF?*{$|(l8pzLQ$sc>RoddW7NfjHfsS0&M(Q%Sn}%2BrR{x;v+onrVGcDld(@j!Nk zF(;7#8VHbF8WWZqz02&EUp`dqt+ka?eR5~?f%b=Ip(@>xKJ7OIe;>r5c@+SgAe9+y zOJ9-3EFty8?ES1hn-T;2l}nNgsgoZcuoxJ?ihyPVlQW^mE$eUJY&h)_X6t-MSJ`Q# zMgyhqI^yb;o-;=W%AWt4lNi&v*|5skNZpKF^q8e7YTt;T{D*f zK(UU-k-;IxE_5|wJ?VsQ7xh-0PyDZQNIV_s?*Aui`Cl2#Avy(A>m3URlamLY%t%dr zL67fkG<*wR`rg30&wU_v!A0?7c&F;Y1HM|if%1n>n}+!~?UkM4;xqgML*2_MAF70= zubAfL*6B*e(%7z*Jt0dEn5PU}U4-aYRQ)xckrvibH&->`YpJnu4NFyWzI0n@(dUal zx_+QD>>4&X8jI)dq4A~n=VZrK-;prOTY07VHyojkGBez`TKiof^o5Ks&&o%-D3I+# z$Tiv*myE5^EPm}|6Zu_fp!0X$!!n@iuf1BD4jA8ducX-WNGiw^^#=jb^gBWtOlh0R zL>~X~59L6fgTBCQs{t24)5f|<>A_h;(h&2By-#OyodcDI$qx+iU!m{>EOS)_bOWX?ShV`1FTKv<}(RU5sWC?4dL<&<1$E_tlJIlda)?c;q8 zlT=?fUK%hItflBsebiCz{^|VKg>;7z51PII>?C#z2kRnIXlwec6B2`D z9VVTGI4#=NZR$GS0sRn*7LtB6o9u_^1QG5lUM+sJd~?=I+r;-pE>nAkr_(F?ozMI3 zJly`#g^5QQ$P!tk$Lak+owgdWJ_lPAQjv1Fl1;ddZcxb^d9)c4uzRxaLN@AZF%Q=U zdwP+VVAbL+U01%?K?ydj^V()uO78TweHU@%Mc z@CJAeL}TkAw9?ob2{V))#96i0?}!E&yHAfEO?fem7L?ySBXQ)+UV&n``q;qIe)!gq ziZ1d0b~*ar%!B_YKVO6YCx<0Va+E%b2;kC7AO@ra(Ywp0c5hEIk0b)*hBCzyBMtQ! zt#1&VR?VXkt}7giZXMhc=vuXfiiY+qQ2mH+%Zod=7m5R)XC^Vz4~X7SKbos7LEXc} zJm+t|((UVEw`_%~N7c_h8ZSgxBc&KL_e#&deiRS$1_YJCpb8XM$ZSfQP5t|2B@h2gv-Y>V@v zeQ&rI6RN-NDGZ63&sXa@@rN3T%cu7ghO=zs_bZcS%m>WFTM}nzGGyyDQ-kOR)O5$k zlbKh3$x7lYJflX22aqXyzQ5AuI4jqv$YOiB8pue1GL_By$m~x(LoPHZ+i_!O+$%i{ zm9j|sqt=$Pjs(zqxTF_E&poOpa_OnbiDRsJj*<6gj-0>eNr6DFpavfY)d!Y(H6XYH z%sIAeQ#W41Ws>T1p&7r!(ztIL{6|;qi+*^F+`b)uT;rhN_>1HDmZ5U17bLnc7z*L| zR+G6qlacM5fe^04LCDgZR37_X*?<+nmE?$mht}B^sRQQnQUsL;&VytH{+wH z*%JG27|SRxU;4PEPV+SN8x^0Ej6{I?oRe}D>I{&)#97(D3iGL&!JSjPYP29#*R z_rdCj)glPN+ZE_7xS72Kcz0k{Z(!45gq9O?LPx}~>|1{mV`J-1rxk~n=WJT`pAwjoFP3JXlv{Q8_xi~%0DZTBULU8%Y2^B}#LkVhbsr!y z*E4iqP}jVco5wEhI`j~L0>u)sm6U>x0qpgz5nmK=&Mk!Mq}x0R58l9qrE=Y%%uCcD zcU0p~h+>MfJ}6Grg#C34B`swG`dC|`usCEcX4d6G{_-MP;t=peWWxi@k8L*)Yd{jX zFJA~*e<0{#RqPtolT~hK(&!s`w&&Z~*Lpj-R@#x=@6W>X^x5rh*YsD2_=cC4UoB_3 z#Zqiy0Qdap3LPG8Z0I-t@Y_yms{xV=RKhZy=kB#+*L3S=281>O4pF9`0k5+4yaJZH&*!NFd)M>6I}}!6ey5Q%0dk#1O^P{sM*Wc_!H_jk zwa+IvgV)$(hNy0GIE8m}wJ#b_Ah318}4v298Pf;<|tc}1OT?MnR)QI*J zA^9y!l@EB6!migxe8}Lvcp1-_jxObE23G5&-B=Q+UZ6K69y2of$*I~tWiRfxslVHL zJ?FT_iPbvyqFhv(&6^Kh3}BmWG#gB_h-xyW{oJEyz6a-NwP?GvBUd__CoVyMar5FC z0CI8=@qiq(ij0OytSy=L2|2_%mF{9XpN9pX$q23Lz2;4~n%2qd`|)ThhmL$6m)$G+ zh{iTeEX57P_`iRVVocn~C5h+&7?_8 zC|%-tdGc3($2QNJQ%OoT5afR=4nSuizvzlnIS7b8dsnk5Z6hpR^GW;o=&Oz96hFNq z=Cvsoy{_-n5l?7ix2<=Iwq|hAF2B!?(2D|4rmwf`;N6bc!_VAfh-5LrY4tI(WPXoU z#l>GaVZr%4gD*4~c3 zUg5=*3+*0PAYS-2*Hw?1HWk9j9fEh%CBpuP;(5$gAhBL$?#&Xbp=2%o#&@Y?z2oXL zV%6-IfnWGBc7tN3!a4RUX8NNlwC908dpkR+_k>2m@ET8|Nq$`Mg<}N6#0%ZP-tU zwE=dZpi#zEk=YrV68RbyAqGv*pGGBd_5Vlrkg!;xk*yscWOLoc=MhVy>J`ApFg_ax z={D!2I{ie{X+JD<|NASg;dkw8lM=ZIi@-40ki)RvbUPu4 zxHgy5iVluX(w5D3k9{sE=e$YG#>_F|d3b&M;Rn?$SnIu3ARb>$E|S`erpgy@Yt~+Q z^!lsw$16L9Mhb)6KCH~GJ9Okwk}WwKI~J4Qh#Zhl;o1GG6mhn1y^J#_>9-%)5}u8u#IGxoV=T;S)+D?&*<_QByE|N4p2nU)%-QYAXHtX-6jsiPNs{AU zs|`WLnTnyRg%3WzHq9~)AGmDz8~J|h?J;N`f=dCC5@CJ9LEkd_1PtuX6;VC%b>UTl z)l%D#_<)U|tu-dJ|NS!f6N}&{0}3n}#f9_ByBN9Xnzju7w5c=08b=XB=Rdk0 z@_7S=7cKyWl_!{MK;-}6QL*h;+q$r*wEn_E(0q#L7U(i zN?VY5u-cFan*fHdt$Xiw^uP_7idt2M@aYL8)Uj zF)> zup6fz;2^5x^uhze${T+jnFlU%N{$W8&MaT8U)4q}p1F1lBneb@^mWfah1r}I0acru ze*sPosp^Y~i`0gyHVpwa@@+d~WO2>GuX=>AolhH$1+IGnX$%{->+AT9`eQW5 zHcetYK=kI{QH}&fTb73Qxa?oeNmE7gPHF6Ex*7BYD}ePUYc_${-A4Z=DuNmZs70l# z-lrkkh`+DYNgvU5K&w`oY!8aG>uCvvTJSIDJb$!)<%qMn-;ee!Yj5q zFXW&ng#G-lZXrVbS^Td7_4c=Ghd*VIDXx36PIa*liG7Un_RK<3Q{{ClJxITMW%L zs+p-j&ba^WX~{1e=3CDfMR--Uc(tYx7h!w^WXhgesJ*Lx&6MJAHhy~@DGP%lS^B$2 zbM${6u#Dw5DtE82KxfxnW7IDZZ_i?py;T}LfuGce_F_VxOXj{_|Ek~qg<(S~;yJ4I z1<>n8X(k`1V63gk&v8|uJv5!hnp%(dD7=KzYy>v_j1PU4gp` z-8yq>+-sI+_nGKm(?k&Mk<=DE?p3FYh1eDQ2E9`}_iCGU6N7mt&mlzeg?$k0_ zl$)$NuevsPY*(xRMrl(WQuE}&F#b{V3+{y6&@h9HKR^2|r@r3k*Z96xN)$@Rv2>fC zr9P$sJoz^XMs?HZT2}!pI88CU^FGm+#Gn405CtSE&dwWfinQbndd(Pn|Sp|!T3)x65^S~(P z6@PV8{eubU4Uba3{4rpNO73hDA!s?F)gHg4jN&))n{*LyQ14Z@CjkLCaOO-~El z6#UEy;r!Rm=B6pW%u}9#$ydAIY^W)j``~Rm$thzNtzQ}LX!#WIOJ!1E5#%E_?u}rR z9`ysdZ2!^4&)igdWDdt14NJV`JrHp({UJ*iFziMR+J*#BRmo>p528ZUNAs}=uI(0` zs@1;ENO0cj>#O^(@rfUuKRV;8fPXT@qN74MF_7rrp*jNQO-&wU;tdvj#d774v|zb- zF#Ro%4!my#mEE1s2LDb2)wL0&qY9q71?t`}DqXr)^HjStUu8zQUFp!&g~2tDi&Z>W zdC#|K$&?~Xn}Ca>YI5%r^4d#xSPUX&Uw$_AsKpxHV@kX5{reyfRl1{+sJy2`06az| z(yh=0r8ueIV;jcfMYq#5Eo^uqc|+IzR@7I1teOb|V6&M1Y$vfO^`g1*@cb0Itjj3Gg#8M&M{4;AR?9$5)~~Cfm$PYNTve#-Ke!K#<@&M;@D@ zC0y?kycCF4!T-_yC|yL%h3x)EXV3$=1J%NM$HM4Wkz^yaFtNdb+Y6U-FWl?C^Cp{7 zWL@LF-foVrFjawY(#_4Sk&gW&J!?2=`=UvMwE9N%DqW-G;q8>ca8aT)uq(xZ_XL0@ za$!yf@csYyeICB#Key9x9BZ}jG5gVho&E}_{;F~m zl|cg~oq(l?UL~glk;-#5^`m-|*R_2c2K>igh1(^cwgdG1&mXz*iVZ!i4L>Q9zIK#vX;OHK!p zizEWPpmYmavf!@y(?CrnD^1PVA=K~ciyxTL$45V#Mb^|O;Y;}_%?P;U|JB}Cz(xJ6 z{nD{?w{#=0yRdYJbR!@ku}et9f;7@dcc-*~C?FuEbV@5yN=PXwC4CqFasGAgd(L~_ z`?=@d&-pF8%sdm%Jk!6K?>rOG<>})TyDY({^UYIXDY;4yFYutVIkVc*+Z3&nnm z&=snh+Sf~j6M;+QS6x>_lfBWO{gby)7SPKhX#8ID(Rf%iY;9t1pFV`S0+U=X=Er;GG^rBH{9^bV}AmWI%bY{%BB{K`4{dSM7=D9e* zqAC4SP1rWv6jQTt;;@pT6UUaq!F9*-ZG0tAO4sDFacgzaSFDCuogf_kp?Kd%)Sgu@ zew%R1=%OQsHqGjY{rw{;2_8vt#QkmT7bIQkW9c2u4gtv1=e!EUhI7w?W8cH zd)~D7HniQNJ)dnYoB}!>58Zav33A6v%`QIFT!|dcGd1YdD<*PRiQ3BlEtTjE9S3v+gbd0 z5c6lm;PEVZ*^z1V-eelA$&gpqtttu&R{{%H(I2}2@3SjYI08itB^rz3qT^O=92aP@ z=QiwyG`R8*_F{_>N$XRI?;$;T#PcsIY51>{IQ+-`A6IzspS)5aMZTVaIYHgwZZHc+ zJOIMzZ215WAk4te@WUW3jtAh=^LBwT@EK}aK7d)l5q4bkuF)t@@ASjbU>N(ZBM{16=e5;JRGKM9Y~sAa zVw|Zh)i2oE?__7ZBm6{;KBUK03Rc$iv-AiKQ%-35i zA^O*3P*y0NsFWecWydFtK$kdMxx-|2iN3^wx-jO*-thsloG)5ZUA+k!Y=5?y=AhR- z>CUb~JEWkRDSfy}@F4ffJaCUy3zyxmp&(;$5MBzxvAJL|mM9Xk1-y&?D_f^2)DtbD zI!dU^d_>>V4PB3pSe~_j@-JLrt zp0A&zAe@Lgx~*7*=l!`E-5x&M2{1`3lTtdMEs**yD3xLAcuCj24EEH#OFkA3%zT*&Pu?9@XW zLJJF9+4(H9vdifCkXjB)mxzailsv8_88en1>1C8go`Dg~1~-xM&O_I4k-fCJsB}?6 znDe{KpNV2cL@`B4o$;IUyE$cYga=0+YmE@&Pg6PhB;6EIU}|pimHDtctc+YQcO}mo z&m*T#ZWYx5Hu9dz=dUcn*kGA%Eu*Rx6Os7DX=VB6Jd793#ri;AG%z3B4V{Qb73(#x zoqh~X$1qOrJo=Hi$=E*ePFz^srjHk?72VX5FKa~&db3=OV@V^m>C>R zKI)uC4+|?=X}ZrCy~LBD?&yyU$mhIHsM#Xu9lY#_*S(YhsANb4)ByG9YF*GGbd4PZ z*=~v9;NKoAjSFKI&Y88W1nz$@ah(HRqaM&Y$a#Qrtg z+Dvwxh31HFZDIg~OTOltCv|@3tQ1xRwnMw;?t` z{j#qXPc92|aYSWUaTh+xHGM*Vxuk|owWZ8%* zQqSFYfTh?(Z}HV~+L+`B8;q2Wcr}0q>lBVDW>Ubs8adoHx;Em*RI4+VAHs`5uHfHO z{5|{1)@s$317oQccE7}VumuhKz>}y{I=-bikQk!wkl-Sk z2G3K&8AMIus9V`F6zTK&ZsCGy`JqvYlE{y0K`1q4KX9ifDN zn13FjKtO?iVT8K=aJ`5`sG_K>h=h!cjAV}ZAYCsZF)4W2StB8-t0QqCAt7NQ+=!7- z5hi5B=f`usg+wIpVP)@)AfO>A;)q~#NODLh$jCQ;KR>AGs5cJ=8X77(CI%+v%@+$B z2OA3ufQ5+(zyknqa1jqCHa-C!F8+=ACX$=rH=z)JxLBB2H$MKk=lTs25f;*Wlxuw}^MDo!QLf**v^Mj0niiVDXND=@C;o&7ABKfGOD2T+OqalPs_D8rQ zp%I}IGXiBWNVF|5nOsRhL5YP}%(5>!$aKc{SiqKU!Po$D3Q8(!RyKAHPA&mKh>);| zsGPil;vFSr6fQV44>Pk%%PXH(*VZ>SzwCcII6OK& z`F?tKBNs9f>Q7?*D%l_8B0|W8f+zzt%p19oQ63_`s6=S!j6e)x8Es4pR}v;r5EiLy zV&RJpY-X^|9+{=vIDni*V2O4AMzkN2{bPa!|FwT00B zN?FW72`@l@5@U%_`ddV5`3S&?vS^h*lC`(9fdHA@L-Hr1YP^fO8$fGaJSUA#I!T>3 z^#uumlq>N3Z6+deR^74N^JFcEf*v@!DUJUmfkSs9?RTeve&FhUi?RDZVOG?-l$JOp zt^b{3+utg={eQ@O>+i#{Ffy3dcXc-kkX(Lfv%b-2{r|-mp#tZhL7n}{uNMBl>4Jr> zm194i=ugEIw;eV2g3e9McPd32(WL&~R|~Os_9p?g|9+kLYris7h<2)85Owb#s3F0i zKVTJO4*pqo|IYr^z~!V!XB?%c8FW&;kLVD~?93&|kJ(oXFtw#c{-y@|f7?Sy|7d1e zQ0%9b}gLzan@4aH(#&_&y277j%9(z4D3f{=d#3?As)E?E;N$` zM2zgxt(~{=!XGQDhlIS>d&(ub;8Q-p?4;W_hS>E@oFFwd}^5)igkFL}B= zSsn482>z1?|EUN6X$!ya4=x)Q>6xdg8vkN74ha08>|X`>|4(fhCCQ}G$l%JlU#n_* z)+w!9NT&=Qukcl}hH713{As)BW&twfA`=;{WOJB(uah!av3SCeVaMp+oe5+S;fk$UiC z#9G_37d1||1bBP+u+Y0kIPWGBH8HTi?MLpovKP{WVBM{EUX!dHUewve;@}>$SRqs5 zn6ekjzjeqt7=|mis0O=6bP0^Oj?|@$j&bqRQ_wmQt>@Tq<`?RlOY#cOGsOu>c+jzt z7l6fezmT_?^9dJHTx8++Ji0A;)*}}!DL;6S-sWXH9--uYRZBr|`-}j}xW054yooNA zYqp2RfR~{8RFxRoV2)Pexw#5ZgX>7@q_gxpbYQ7$PYjV|Y!v$y^C~CI1QVsqmZWog zohog2ZDt2NiaS(wn7~P5_L1>DE2S?uXrS=x6h^}dPWKXfKA0oTl{UMJQYC3$obJ_< z^5wZ9;_~+AXH|)2%iB6nyDTd%d1KZK8FYugC&mO>u5s)JHr@A;IbT)ZK;KU1ek7nX zE`eS`gvh(NGTh@?C}p&eDm5c4BUl^5*AW!clg%2N08;DV2x)^*@nXS?3->M8lJ&Mu zjUKgpuph;_lNgn$sqn}4brkEpL`Jbz9Z%4tML zZAFXH;jWbJ(?Sp}lktm(KK?G7SRsiLW+PZ>{+t9lKgI(c<8BX@%Hc!zmNKMXY4r!u zO>xlF%mAko(vPKiwT)t97HA!jQ)W88v~00cr8=!K_{>;}#m7mrEBtoFkkn-uoKl9L z>1Fy<06u5_lJ=v;VEs~m;Y&c5VV>Z}G4x8cbC)x+@E79tt%W#uGmn=JxgQZi&@+v@ zqp4_;*0~RZKZJii1++0VFnR}&y)%QLMY`gmJ>bONq;$~$2;`fLg-f$@OJJFOHv z_%QL<$FoH75pE{MK%GFY_DA_y6gVEbM8qid81LI8vz=x1kx))-AN+j50@Ezkw)q~M z9Bf_ULt06D=@tQ1pI6avwu+2#iv)4iY!IK>O~nqO_R61D$}3UNq~MEH*U6K?YaO*` z8J#E{j!y3-(+S8>&ookNA!RFCapdyIEuz>$zaHO2k7rutVPy;BOq{S##Ere3FpI+B zJK1K$DZbi4=ci+g1aoc{;tnelZwiTrh8Ss4cfgV9lwE9NG6T>D2mLMu2dc;k`_U*7Rh|! zaf_6r@2LDZ0feJ%%PrvT2`uG3V}cV^zVDld!Hq~FV17|^4qMEgn1V7MbsLB&63ghD zjYowAbfn)ZK;|;r=sQbxxxbvjCPluVy zw+f1!m7e7`FS!z<^@MLT-R>KI$J`GG1`qG3wUqI^O@c!bQPP_!W!UIT7j)CCl|{3a zf>cdo7?KGlcQ{q+SaQ{i-<0rLlQVN{@@Ip#D^e-UV@lp9N_vY9m#XeF0cZxwUNi^n#pMwwIqGo9@i zpfw$5>>oGvf9&xO2A<-i3tm9~YG?xe#n7bn6LLYz!tDlf07m%GLCoBBVD8QyZdNdN z27#X`a?Vb0gb#NH;4gmEVNg2@S!XX}gb#iO2oS`}&mbgn^J|KTLC($D%BnE5FefB6Z(iI1#|Go=1O)P&aED%otXc5=3a#qI;!JB& ziLEh$6*|Q(z@=XHbkdB}qKVZ6rE#ZXV{frwZ{`wz&B04@fr}+O{o*vLB{Xg1oR=~w zhT}D+WRWr*RTD@O{$^ysEile2iPrqjw!*S2OSa9lpJJk0DX(p!oO+{~@^fAmHZL)v zkD|SQDWnJ*@EUq*kxP9U#bJ<{B*^y^97}0P93U(3VEk_NNOpmzMAj+RORQ;Qp;^07 zO=9mZip82p!mP)NMQ36dXN1{t0$uE&t$H}h+`#C4`*&UR;j<*StC$GOAjPS9IYQlT z5WX0+lTeCR>7{5aLC|D9XwEk_)%EPYZ<}7_V{fh7M5-Df*U=9G#Z&V01LwBlv&+l@ z9}K9KhZ!oNV1>j{_B!;l{eb-df%_wbgcirAE54p$S)+bp*g*F< zv~DFMD@e6xK0)?w&`kSmhgl%U>uv2Y{K~+N*SE4P(K*V6RiLHnLmj$DTF)BVDbx$h zX9l6}#rjW7C|0r`@`q!Ws!%>bv3~B1CI3do|9$usXMRkC$i0t6`-j$$*N!^ki`@)v zdp&3zWz;5h*biDLAh`=n>Vjb(=KXy~1}5n!*~=(%f^c#`(0Y@cC_F1-(QyoDj;b`>y^#OI&NwsW}l~ub(@x% zn$fPNiR3G*ZN@RP9$tc{*6I#OD3K(WuQ8Fnfiv))RwRC@FByFY|8~(NTJXs{S@Oak z*0^D7AlU)g1I2dSQ+DBFXxX=;ZsZ7T8jUz*xy~4MKa@Db8ff9L=D6eX<`GX3=a%9* zUXPxr4Fyy}FKcZp_(5N6ED0nd7z9veB(pz|uJ>LH@5M$|{V?VtU|C|O`)uT!ifV4| zs)SAT7$qsV48435{m#YrZ%szKaed>&4-%`N+wGcOvF*utMtnpl3(n#+{V3f(Xj7tYV{fr@8l3eSd^+lHFJ4P%z>_NJuRg<*P&8`O)1caYziXnj(5B@`E{% z_e9Q~(>+ffXs8e{$ROFanD#HOcTA3cQZSmyV@)se;|x((qCYY%#@-$xnb6@ z8{yU{@;zng4Vs-vKf`tHjMI<@8BE<_CdM8+wx5;Cdapuaub&4JKYH%5%D7luhtiy3 z1{s!Ce{4s&=}G-4bdsA*-E_+zR^S-4)~|p&=zpqpmLgd14dORGx3hQy##4LBH-^+{ za{gAAG9&!p<%?2aA@=e1Qtjma))r=0qBE)xeW8D{(TmnVsgv!DZGpn=FWJ#Vw&w*s z-yAsD#F-!_8}hN$h-SE1EApzi7Mg7+7KGtSaZr4jQ1JvwC%KFm#JV@7$W8a$%hTuaGqa25h^9 z9RW@|W1}&Q8ftr2%0L-q3mEhQh#psv!quG)5I4=~R%gGR0uyzy=(eLn^CPRsS?Zde zxwIt6XvIZpWXRmt}UwnCJ$ zzKM>{$9C$lme@XolW1Cy$i6B{Hym{o{}5m+TV@6ot669mNZl4TWlxc!wwZ5MN1MY@ z-#5-VatTBzf8sAIR=tRmxpEy4YsD+i@kNXx%aeR zo66Nuwu)+%E7(Xbfjk^gpCu}}`0*o|A!z4IB(ZH6e#YtX0m(dtlR@V}I8^icW5~{z z7#|QSx~u8hwgN>Wpjb`{P4Ppu7t3G^h@z}NmrTou@IWZQK~hHU-ZKOHe16^IHc|Da ze)i;XShQN<2U`#Q=q*`ZCjmCFF#qENR{1F*!Td*Z-<=?)agwp&2jXn*NyqeKwV1N* zo&&yzqbqA#cO$gqO<#wxC~NaF5y+F`G!-h4OLUf!j0fTNo>Nj4Wg~PgFq=GSX2~?+ zU00?^SIKx3EQIrA*5NXmH@T|4*9W5Tvif+HD}h3`R%iyQ6z$i1*i{=*=r7{R7%~nO zz#9(j&%m!DAMz89Gg*FoHxu+tpNSJcdn&@Dbz6Fx(=S?@5v5*0yZvpkPvj0xhdC-H zE!|`5A#*uGj`l2c(z1?fYNwY`uAi*yn`p?6FnivDz9r^oBhx|VJ$PXDG zqwHj&%~W|5h1!E==^eQ+ceR@W2KRBZ&gT=?f2<)vuvgdR`U8LkzRBI#Hx z^WUA;i`RKE>m(#oNQ_|#wSy#|yY7wQb|hTp$ZH9 z^oWBHEsOrCd~sQ=e(;JR0ap&9Da9+EPp8aHODCJ%XLCSkon&UYmJDeThKMWMawJZI@@2di}&PDk|qSTx=S9Vyb?~@{V^n5g69eB%Zo_IIoH>n}2dl zh$-S8tWNq&D2PN;OsFr%fZq@s5 zcFita4u&+Z{q~0<)f8Y1s~=w%z3;rwG`^NetT?)<^=&^($d+7j(7ad$s=r@gm^m`- zR@YKbKj+`(9R9iInV7$1LyOP%V{JZNmN25HCgi>Erh2Fpl}+Rgb;DmV8czzl+?jJ# z!=Q&=EkafE0NSJpoXJtGE{$*FFWW^VO8gYo&`WfWK9{q^Kn3Ey6hhD;|j@p6w%3i{nAM0cD9>AnJ36QjKdnJ86sCkbH!@o)hqIRmlLW9`Zf`uYJJxu@D zfE@Ey_e~lyc^@YZCy35my11+4^(UgMYK#zb^(Zkc=f@`W#!Ygfp?Al)Q`A9- z1BKibVegUGe3Ja@Et9sabd+ktS-4-Sx!tAKesfz&-ystUjbM2QO(Ftt?YCczg`M#% zYSS`gFU@bwo{e4Ae2nn2Rq$p5UYVSDBtSDHKa^$0i-u+Zl42O+MXvmqKrzqN!im_j zEW~KCRb^lw8b8rWYetmzy;6!DZvOX>Y37{5@# zn!i}`qtGsahj7+VMm*0(=xIW|AHGOPC>4{1VieqoFiI)IIdIZYo97D$jI&_e+`&am zu8(J1wljkE>tu<@%kE1aoD8o`Kvx%&-sX2&=?r|QEa`+-{T6qlw5;f{U8wVV?IOhl z#wMh?zGph5o9QDEf` zu;8!L#0jw|d}E%(REbpgHE#VJ2R^okT~5lgJ+XKXRh`2&jw{l!iJ%v#SX_Y%dNC&` zH{D1#gYU7OomO>G z_e1U;v#P`a3wnJwG^|vw!prZ9^oUrVuQimeiMXFAnm7A0E9oV3sN74&B7RqFIxG>H zZp(+Y=RO%&Z=bTqv}6{($r2pS6%>YE4~?-4AG(xGX$m^0%p=3{nlo)~xtetRUgdJS zSEwdA2J8KnXqPC(!pr_`3Ds#Qi$v`_ZIjY|hxsv_lSN~>gAk+yTmhl+grudg&6nb7J1Xj!o!up5Iah@_D1jL&bP|dzALej zNU}iE!41*k#cISAd&o8B-`)vzJ)seXeIHY>_xySv?SUqJc0fVBc4R%^%@SNQnrWJ{ z)_Ej90W+Jt`+0v#(A?ayUKifv;b+Gfkeb0z1ll_v zKLSg06@$0CGMyA5`~yLd7QZ$jZ5w-~RN$9G>&S!V;6bmR-k$8?ebbpO4?ly3SW#nC zE=c&){_W*0xKDJ`V^JD8E>IGmqfOb$`PA+18A`$<4?#*(8F%wgfxa#+s|Sna8cH$K zf_k&u5Rj~!nKGkk;RJQJ;ytAi&fCSpspAZw30XG{PR5?c^(aRG!Oz5bjX4RuUY`~5 z1}Wd^$L=?WtN0$psy?mEI#9vk)F=_$ph=&qa2nxcF6KxbFR)=*J~~hZaH*8BJpn5Rw}tp%AhSFjk4gd1{GsZc@9Qm;#AjG3aL&^KZn+R1G!QF zCuxe55d1;J9`N5Jk;)Eyi_kzxvsom`4dp%hw zeoCvOzSTdTKd7@y6ac$8n`{hNjLMexyi!SUnw1Y`TU`}kyXBcQ437}%=_Tn)YorT1o`A$ zAp%Ul>wd?=KhuBbK0||dz8DeRt5MUh#3t55{yV(m$A(ktpXP2q?T#Bd{c0bWvSE#i zXHXJN`);CH7%n%Fw{vFwh;SLRKJ$k; zEG;`J18fuUTu(cN$*!l5N?5ct6KoQh#dbPy>!~w4hJj(kh^Lu1ocs za?xY=>-`GgS|Ov^v|h>4@HZ#%fQ0M$OQ_zXO2BN?>Y4K!$H&Tfzyxs6(gcJLN2auV^y z#_%t^j{0=jCU?n5_0QLGFyNc8$3r7{5TB5of)T#NKKX~ix*rAbKbMy;zg!p2a zvyMB}#;mE<%cp6uy{G@w<yw6)9jF(dP+%fmuakI=S1)$mpwhc9nm7SAFXVcC`1s$>Ns$* z?Wt%TW=e25i{z8S16e5Bn=Lu#l3N6r%R@M29Th|+os>$pbI3J6m^nY5e_j*qCb+HO%8gpz%~JdzX!B@Vb615vg`$HrZDq*a3RPS2-l zO%sbt;a);HM_THJ=J%_m5ow*hX2VizGh=CKlDUUFHk2%Es1xhCKPB@_= z&KK=1jS@u`gWCku+>H{+9IV<-9QBT->UF!)Mpg@-7PPcaQ9~8NF`lCI*Y@*|qOmd% z>O+lUTMmeLd1`AFAe0B|OB1(R6bc2!MG2k8vjD!tb-2>e4OXMW7|cLNObbiLJ8Ldf zUuW@yL5`RN6n0`*{V(aznrA)71nc-{p+-niM(Rraj|X3+eso}^K&SP{>UH_4`=-<> zGy1sYk+tghS=7G2=dN3)L`&{k=Un*+rOwgOm-6~h2uo&X9HT4uu$Kqr)9ieZnSmBn zO}neI$K=EHR8EJKp~sz#Zrq&fX`Bu*gLbm_Yw)wI`R)c-DH#hgTIpFXy-2(nW9CgG zreQR%VmSP`PXdUl%^J0-)Hlf9x#GAtKj$qrcO**>jo~*1P~~%H)N3NT9^SwacHJq& zAcWe0p?CN`gwrl&AX4VelS?T$Heb?TzfJzt;md#iZSv-L%*~d@|NPtJZw}u4ZMy;t z6#PftCR2@6-RBgE+gk>&k+YL^-0*D!l^IvxRvq?VCHBS7$(JrA`Ku%ba%K)9FjnqUo9d>ehgFGeto` zCn4F>HL|jJCeW1TPilPA1*7wwriT5wr0TZ|lhAdVADR#`t7);wux@2H%dq&3OY0~% z0XQbRImmH)N2>OEs;XC{6gOD_!PghUP1V)J;Tu|BLk&#?iLIc~kPD$m5*`(B=ajYf zJtB3QCtNY^eG(t69=~kp_qEroHyR|@K+@I!LNg)0_Qhvq3bgcwbOb35Kw zy@8`YI|pciw8-aqrv`QF*D7pNFejPr7sIkNpYTw!C%fSXS`3}rlywO31=|X%QrOY< zC{wYl*d?ZDCn_50B)ys2*GYcS{Yd7Wr>0cA4&g?qhrO)lSJ~jc#ioAeq>`)RLwOKu z;*qf_J(5{k@12-yY1t|^l#S&eYi*)CVa`${EK^wNP)Q698o-j(vJ^8t3)Kt8+NcNg zMFPmOOfrVqOWj<>jxl~LvNE^_z)fgQx1GSyT&q$1m;j%Ra^f2Lw~V0f&yoVN4PjaQ zs)@qv+#@u+MdX`p`Rx6$6x%QX`~ws(&|7v_`HS1CGF37G*y`+4bmXA%PL=H#Q)KRu zz5#Vn<)GwOcTS^u?c9z0y~U8fM!rO=3V{$kk2AjWM%4N?^49_nv_rIN=7PpHL9{9> z2(gyTDbXi{8=MYV!ZjPq5c3(UIo+Wn%KMWTtc|GFVtpAQVlAFAzRRiyUK4s0g@-sD zPe-X(6(#SN994QDt}xPzRKNGy#;)b=HIvzw;B$TMci(nvOig?2FDt!n*Epx`zkUt( zAmUs76c>j9(2W^e$tqE0e>QnWr#fwW#MgkiZuAvFHff0?y?Xb4L%h?4h%#0qVL6zDQ^CDoJi7=~{&;n^%ehAj*`IXSDTRg}U7^x}Du5$w8D;+9 zIntqw3E@ZufeO3UD|2|XWpOUGoUp#+*?5GZGHG3ZCsRIa5oxp)ZL{D>;bUj|kh`Ro z=OCUtoJ_^?V(Nw9m?B~_{|M>#g3aW@&z_EWj2S)L!+42~vyf(`9sBH!K$5^PyO1}~ zvy10T2(BXX0@68yFcN1qD;Rbw9a0xboxA+X7AP!}K3i`|~{-z;w;!VjkXF`KbxjD$4Xhrm8_ScbQ^cn~`!O z6-XbX@#8WVPNr`XjxY)nSp>MtRbakW9aZp@83Ba8B4QG7xIu_lPeJA2nrjv@g83cYtimV ze9k(c9DS$yc2}V(JZJ`YumNEK$PLHFG zJ9=H`9169etig;gS-*THolW$to}ZH`YSmVK3`pc_KmH~ZTc7?c+-uQ0*l<&nF(=5M za*RV`A6c2OH$fPV6{^Z3-$L0ba}top6y=UZMUZ%n-`;(In_K)SQO&BaAkA*7R>%ui zIV!BOs@wyZ(ZK@PDp0CfcM*1aWMQ5941KC2K9`nNa!-0t-H9lTq$kn8j_IDCBGp@y zQ7Fv`z@H*G>PZ-}mTbI0#s=X?n$A5~8rpe=;Zi{E+frw-Tu@e`c7S?s{=L1~k~zS& z9KC@m@x3orCT7EBc&p^09p+zTZ1|m7IL03x@Cz_LP6knpR0f3TpqUfLeY|xMIF@2Nsz1z*QS^h7()%+)<;Nz}@PNN4PB>oF9t< z&*r;E2rD_wG)O0FHwvqfH@QAu!SCV9b3sFkG^Q@JDNl<;j)x@VYs$lv!h7M7cMq^p zWCBYv3{DO{lWWsg>AEFf5^u<7<7W@kNn{D_;??5F6thq#SF_fo+ZA!VI8#_L4h(#@ z!aUs7g(~B{v9l+wjZ%4QUgSFr55`4l;5g*5zOGrjI4ouM9+~gK#{zf=b)2KAPuYb* zwBY@HRT@L34pz@LJbZJA(HmLx)qZmtmHEadRXFrZ$GYYwe_-?kvG7t?z)aG&bb9}k zmXPK0`+Zc*Q~;I&(i3f5vyFm2N_b;9%|5v>?P98Q!%}jIo;-ks>lBS8`_1Rb2Cg^a zD*js#**E+&OsP6g><0A;=e7VG3tHSC8ci5UQ^Z0Xf|G$?>iNOp6PU5Dc_wgSLN3va zlq$Ppgk2us#I*YD6rstQMiu)HtlkT9kQcQ@sjVJwqQBFR)pOCk4)Y~_g>q=laOVj3 zUf7BgorhlW4%L&_FnjEXx)w}v)mmKiyuqiPNGf5sN9XhM4Ey!ii}&b44{xvUT?z<` zZX2Ovt0~l+Pl<_&)4&OQ&lk*XgoNkla~}Ghzoy4<^qJ4peYRt`yPbd3?5~ByN1gSB z>@J^7{>Y3O)$5j~bD?dxl$z7Yk#gQZw6QjApx>uY`U9@liv@w_8oPNJBev&m=Z&9t zMzY*1iezR=DZc@}n{Lw4<)mwB-p-2%W~q04<-0b&mJIXOENr*5#|-9=uDyGLvh%g; zQ|M}eZeeQAV5PO%DnzQ@s#lm9E3nN`c$rrU7 zZ+$SO@&^g%&F81LB=>DUeEgVCbL-;{+{>r1 zX6Uj{wIT4tN#^AGG>Ps+pF2Ty`(2=ZuPS9@Q1X{kyBGH)kglZgI=>)vS^O1JfH4@V zt1)PKSUT9b+ajofybxXx=kMVtH#nES+RSiAM5k&8b!RZf0|0MWB4(Gso1KrF|2K11 z#Qz@%7-x?gM9uFby@AvGEmB0PtaM>;24e<3EqO%-K0TNh{0DkQ5rMX05Eo}auu|k_ z0R95PQ3N7%h5W?fM($r!k^Jma*^!^&XIDODM{!`Rkm1MCfSakZvLl4y2aXkCx#9EMJpPfEze(^@+1w--5$;!gC^u9ff*@<*4*MYr z;#h!#Ck$?9WpP{9*#U}hQgA|ON3nCVVL&KEaguRzxBG?g2X;3C{R`~=aZaFL4@MX> z{8WqxRWC;O7(jvuL=#w8gcl3}F*tcRINTJ_FFRhKU(vq*L?ACWm^B`Np8*6y{Qmkd zKm-NBf(%YS(STqPK`^4&Z=T=Mgb@Yy2O0!|DBM5Lgai>4^S87c^G*5wo+g6e@&_6O zak%^s{(vA52!d!0zYPlnfkpmI6A~2plRse*$e+^85BU=hFu(9k3H*)+SVTnN59tF6 z@be2ID)#UFfx!rn*dJ*ELVuz`M1=m779m7d{~@dZKM+yYzmHFVA9U07e&Gvuv#@i3 zx#0nRC;nUlm)S}DrhR=0sa>XFjlPq literal 0 HcmV?d00001 diff --git a/CSF_ejemplos/TORS980325FH2-tax-certificate-1756664608.pdf b/CSF_ejemplos/TORS980325FH2-tax-certificate-1756664608.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8ef834f415471e33880b1ad5a0c3fd97ed68554f GIT binary patch literal 143089 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y 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 z 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%0DZTBULU8%Y2^B}#LkVhbsr!y z*E4iqP}jVco5wEhI`j~L0>u)sm6U>x0qpgz5nmK=&Mk!Mq}x0R58l9qrE=Y%%uCcD zcU0p~h+>MfJ}6Grg#C34B`swG`dC|`usCEcX4d6G{_-MP;t=peWWxi@k8L*)Yd{jX zFJA~*e<0{#RqPtolT~hK(&!s`w&&Z~*Lpj-R@#x=@6W>X^x5rh*YsD2_=cC4UoB_3 z#Zqiy0Qdap3LPG8Z0I-t@Y_yms{xV=RKhZy=kB#+*L3S=281>O4pF9`0k5+4yaJZH&*!NFd)M>6I}}!6ey5Q%0dk#1O^P{sM*Wc_!H_jk zwa+IvgV)$(hNy0GIE8m}wJ#b_Ah318}4v298Pf;<|tc}1OT?MnR)QI*J zA^9y!l@EB6!migxe8}Lvcp1-_jxObE23G5&-B=Q+UZ6K69y2of$*I~tWiRfxslVHL zJ?FT_iPbvyqFhv(&6^Kh3}BmWG#gB_h-xyW{oJEyz6a-NwP?GvBUd__CoVyMar5FC z0CI8=@qiq(ij0OytSy=L2|2_%mF{9XpN9pX$q23Lz2;4~n%2qd`|)ThhmL$6m)$G+ zh{iTeEX57P_`iRVVocn~C5h+&7?_8 zC|%-tdGc3($2QNJQ%OoT5afR=4nSuizvzlnIS7b8dsnk5Z6hpR^GW;o=&Oz96hFNq z=Cvsoy{_-n5l?7ix2<=Iwq|hAF2B!?(2D|4rmwf`;N6bc!_VAfh-5LrY4tI(WPXoU z#l>GaVZr%4gD*4~c3 zUg5=*3+*0PAYS-2*Hw?1HWk9j9fEh%CBpuP;(5$gAhBL$?#&Xbp=2%o#&@Y?z2oXL zV%6-IfnWGBc7tN3!a4RUX8NNlwC908dpkR+_k>2m@ET8|Nq$`Mg<}N6#0%ZP-tU zwE=dZpi#zEk=YrV68RbyAqGv*pGGBd_5Vlrkg!;xk*yscWOLoc=MhVy>J`ApFg_ax z={D!2I{ie{X+JD<|NASg;dkw8lM=ZIi@-40ki)RvbUPu4 zxHgy5iVluX(w5D3k9{sE=e$YG#>_F|d3b&M;Rn?$SnIu3ARb>$E|S`erpgy@Yt~+Q z^!lsw$16L9Mhb)6KCH~GJ9Okwk}WwKI~J4Qh#Zhl;o1GG6mhn1y^J#_>9-%)5}u8u#IGxoV=T;S)+D?&*<_QByE|N4p2nU)%-QYAXHtX-6jsiPNs{AU zs|`WLnTnyRg%3WzHq9~)AGmDz8~J|h?J;N`f=dCC5@CJ9LEkd_1PtuX6;VC%b>UTl z)l%D#_<)U|tu-dJ|NS!f6N}&{0}3n}#f9_ByBN9Xnzju7w5c=08b=XB=Rdk0 z@_7S=7cKyWl_!{MK;-}6QL*h;+q$r*wEn_E(0q#L7U(i zN?VY5u-cFan*fHdt$Xiw^uP_7idt2M@aYL8)Uj zF)> zup6fz;2^5x^uhze${T+jnFlU%N{$W8&MaT8U)4q}p1F1lBneb@^mWfah1r}I0acru ze*sPosp^Y~i`0gyHVpwa@@+d~WO2>GuX=>AolhH$1+IGnX$%{->+AT9`eQW5 zHcetYK=kI{QH}&fTb73Qxa?oeNmE7gPHF6Ex*7BYD}ePUYc_${-A4Z=DuNmZs70l# z-lrkkh`+DYNgvU5K&w`oY!8aG>uCvvTJSIDJb$!)<%qMn-;ee!Yj5q zFXW&ng#G-lZXrVbS^Td7_4c=Ghd*VIDXx36PIa*liG7Un_RK<3Q{{ClJxITMW%L zs+p-j&ba^WX~{1e=3CDfMR--Uc(tYx7h!w^WXhgesJ*Lx&6MJAHhy~@DGP%lS^B$2 zbM${6u#Dw5DtE82KxfxnW7IDZZ_i?py;T}LfuGce_F_VxOXj{_|Ek~qg<(S~;yJ4I z1<>n8X(k`1V63gk&v8|uJv5!hnp%(dD7=KzYy>v_j1PU4gp` z-8yq>+-sI+_nGKm(?k&Mk<=DE?p3FYh1eDQ2E9`}_iCGU6N7mt&mlzeg?$k0_ zl$)$NuevsPY*(xRMrl(WQuE}&F#b{V3+{y6&@h9HKR^2|r@r3k*Z96xN)$@Rv2>fC zr9P$sJoz^XMs?HZT2}!pI88CU^FGm+#Gn405CtSE&dwWfinQbndd(Pn|Sp|!T3)x65^S~(P z6@PV8{eubU4Uba3{4rpNO73hDA!s?F)gHg4jN&))n{*LyQ14Z@CjkLCaOO-~El z6#UEy;r!Rm=B6pW%u}9#$ydAIY^W)j``~Rm$thzNtzQ}LX!#WIOJ!1E5#%E_?u}rR z9`ysdZ2!^4&)igdWDdt14NJV`JrHp({UJ*iFziMR+J*#BRmo>p528ZUNAs}=uI(0` zs@1;ENO0cj>#O^(@rfUuKRV;8fPXT@qN74MF_7rrp*jNQO-&wU;tdvj#d774v|zb- zF#Ro%4!my#mEE1s2LDb2)wL0&qY9q71?t`}DqXr)^HjStUu8zQUFp!&g~2tDi&Z>W zdC#|K$&?~Xn}Ca>YI5%r^4d#xSPUX&Uw$_AsKpxHV@kX5{reyfRl1{+sJy2`06az| z(yh=0r8ueIV;jcfMYq#5Eo^uqc|+IzR@7I1teOb|V6&M1Y$vfO^`g1*@cb0Itjj3Gg#8M&M{4;AR?9$5)~~Cfm$PYNTve#-Ke!K#<@&M;@D@ zC0y?kycCF4!T-_yC|yL%h3x)EXV3$=1J%NM$HM4Wkz^yaFtNdb+Y6U-FWl?C^Cp{7 zWL@LF-foVrFjawY(#_4Sk&gW&J!?2=`=UvMwE9N%DqW-G;q8>ca8aT)uq(xZ_XL0@ za$!yf@csYyeICB#Key9x9BZ}jG5gVho&E}_{;F~m zl|cg~oq(l?UL~glk;-#5^`m-|*R_2c2K>igh1(^cwgdG1&mXz*iVZ!i4L>Q9zIK#vX;OHK!p zizEWPpmYmavf!@y(?CrnD^1PVA=K~ciyxTL$45V#Mb^|O;Y;}_%?P;U|JB}GfW^^e z>%zD@Bseq#cN+Jg!L3OMu1$d879dD)m*DOa2oNB+C3t|~79hA2T!Y^tpUh|TpP4!5 zf9~9Sr@E_q*V=2#+FonFwaTmA#oZ=iNrYW>f~VDFb@TKngst?mRpips?tY5dJdXv_ zW#V_09~N;&ych4^wBPiNc7?5Y#%{ySBEAVC_4vR}>S7X6*Tp>O!Fq}B!MLv(YBW(b z55G}!5z&<41O_*Sq5DPmhIbP9eF9;agsUQXcVMzDO9Jh}>zj*abaV4~X8Y7a3Oyya zO~Z3$#xn$5U2ovo51s1$$!*4bOU+T*=7fxkfc}nmXT}lq@ySu;Fqv&F!3Riw&z91W z&J@O2mm5b4gWpFm37muz$AYnN*xf1W+1^WAt+k7hI)7yWI` zI>Ix4ni#h|V8%UtA-KLH@Ww;qAY#4UfXMaj4gQ<{NIy@b=WCLAD0!Ha^a9MQ4@+x_ z(Lp!V%rjnCS9Y08?|>KWc2BdtF*U$(C6#U%-ZP2DWr#4grAnd@pGG4))TVn^E{wgP zN4fYeV8=-hS*3R5_${an-JHMf@jMEAOOdv)QZ&^tUO>_De4c?a%-&uKNH zc5t?}u3FQ`5=>6$Ctit(u!sr+?{A~O!f#g^N^EVg@`9a2oJb@L&dVHd!JAbcF{3da zj=nJtgm(9G#z?!r*m=3%OX&|-i1tP>=eT69iv794HbQ;! z^1A(g{}$(#OuDr|Ld0MuuGeS43m|~URs_9U6}(vHr-1Fo!gg=0cKer0*c2#_OG~4h z2Ca!8RSfIwbgjF%ovv#k%c)UQw!p%TJskg|C?jy2Ob0aXfr{%=S!Hs9BgR_SAs@irN%+D3pnuS)q2AyYV^m z(+=JmdlsB7R3Y4lkBNo%VRBxv{5Op>{8vUC{^S3T8@%{WSxFPX-cCVnOdXvZphni1 zXn@n!*b)0s)ZT76mETN{{#Wn&ifXN${$Ap}=L?y;ihN>;mrS#t7;`S3UT;AVPmJH(zvF5kd&0 zu>hgDJM}J-i*7k11T_3QuBVK#)hyJ$OGKemeyX{x8!?r!i};MCnhAP_ zMoDN@K-4|cqN;a9Ke(kqn!4g!zaVDd&lMs6IKLeh^yC@`lJQ}R z5WDoER(*vjMxNG{T;9tIT)&`6PpDg9?)O!~U9knHl^PK@t}CBC>S1y0g++}xLKuOR zDoJ9CFkF%HR^b#9E{i&jxP|V;Axi(YRQ-?zlY$tJrs6nQnGHqrE_K?x3|e6AQ)t|K zv~8UTcYlpAnoK@gZ(XG`ZhdNo4Uy$DJ4hf7)HskC$~gGGRuAoI(tMtsYYUa&Svc<7 z7|zWCseN3BR^8|i79@R$w}C6v<8s}sU9>67N>Ij2ky~+%LPd zw2gT=c{>ms!21EtTN;OK#mbja&HN)O??k>L*Bi1Wp|jcTDVio$Y2Y1yemRvX0tHBG zzU=j&BF)eTfMzRJucKH4-Ci~(@__(NwXAfypsCQ5bcuX5P7uu|e~hGhn8sL<1IYUoR>eeIm-SRd@ffYb~fSF0L6Xb@HI{115WbV~zUM z+uby3gOsWHz{EBIouci>FO*-i_ivv?hcd;)@I_g%BhZq@EYP8oyZ1t7(8YTKY3h0; zf-(-rP9;<;sZd43#)`S6U2qGWK7|%kJOXves@o=N#uZ9F;f@f2g0dIvqTXk2!pqY;+yAT}sK2=E+llrL!D3QG_$Ve)^Lw3PMH{pDv@`ag z4DPclh8RqX;-ae<$eM4B1KZPYuDp3g1&7bs8HkycRT=QfEfVA6NyzanU(-QJ!QMZ4+;bU@AeWe1Ghx4A21!E$uSoH6f_0V9C|D<9%FQxQev? zE27lUWmLRDb#<`lJvSdS(th@Jm3|0bI$RHtech|t^yBT>w$vCWG#ZnRMYuVak@qjq zZRJN8qr-87(4%!}D$nY^O9;mvv?1Zff!4YYO59%$c6SmkC5IF824^8~ibXyf5AzUZ zYrz>bZ7LT>pbb9%^GFR0++JXBnBcgsl)spu1pc)NN=j1WkJA$uH@CpA(-Yqh_s`Q4 z_{Y5UuTD?5Uv3v*aO7p=WME)nVPOn`56tZ%47IeYg&7Qtk`fFP3=9kk;D-kT54d1~ z&yVAF8wOX(+2pw!KtKQ}!a%SY7)cm7SlGMo&j&mr{M~_sfB=t(jD(DQccP%8qoSao zp&%opVWOd-V*m#-Di$^-2G*VXE|R<8ccFkU1`0CD9mhX++KvX2M|c_-)32P_;s0wNNSB{Xz^p&AFsK0G`ekXb|oKqy#Gz#j$y7ZHz& zQyd9j%?O#=o&fCgIveGIL`Ca8_2GRQE@KB@RJ8kqM8qVtbo302Ox!%YeEb4}l2Xz# zk7VWKAsUaLXg<}_HZg^oJu|nkbaZlddEx5j?&lv67!({58WS5ApOBc8oRX89mtRo$ zrl`2`eN}Z$ZC!msTYE=mSNEr$-jUI<@rlVVQ`3t}%PXsE>l>R}2Zu+;C#PrM&oA!e zf`x(qNvvNb`-5D#fLw4u9Uvgz$ps7d0yyDu5fG_3k?_RTkd5r|slh%d1QM^aD_T(> zaH;R#Gj~C^S!(hO}0+k1k3nK<|ef8f&di-~h zBL4~hchNT3+%RggII#0a`TyLD3;x*`9sJw!GsZlu#NN$Rdx)KrLYOhO4VZo<%3~mM z%0kQuHwG;J<`NjcLSuwmFv%Q9+27p&5GL9%p7)4rpTO^Vk(uSqJVYcICCM1uf&pN1 zSuVdVM4)7qtUFxJ)_e#lIR`f%V&z0LKt>XN_bAX0P~C3`%O^SpTNl3W$mICrrVg zswr|Obf$vbLFnaJoEp%j{@$qyY@Gc`K()W$CjOdNoEYe*N?D=W|3C}z1^)r47=HB6 zCim~mugLX=0Oo?B;5><3tm_pypksfK4;wU_th=)JAS&FIJ91sE8 zOyDE;KNQ*Doqhgo`Cv;qFe3}n|4lp2f7H7K|2x&Hf342s8_7qs=KE3CqvYDvvLx`qN&M{Yw%J0qzsov zXG((5d!3bMK>^)UY5$4fKV|TrcJTjlU${BN=06v)_zOiGC+Gj<(8?{q_nV>he{`#; z)rLk33;yiZd-^3QM!~d1v@z2T(sI&?l)8qeXS}*RV}9Hpevr@Sv2s{E54!RVGZrc% z)?L|-)=3XLNzZ!Y5E8A0yB(9o=A+r?>NE>z>a3OpgkKct+j?xWMZ6U3D1gvcd~$ zBHQH{4KoXQ5`zZ(R#(!*4z5^d*<^JMl_~tVX?{?KRiV(y~0|B#nx*@8kk3}7S z!6&PUR_v)eUcD##wmlKPwN>K6!s4J?Op^Tz`tBxTb|-s7)?`!OvQ{3Lee!hHDhaufRr7aW!+?hM&c&33l&}Hccsqp7_m21Qw{}><9c>G(isd63%Z z?YzFC4-||n>+jFYTFYvlZqYuY_U$v&*I<+hrRv%bFjXFxC}@gbR?8=13?ynNj1hQ@ zo_zQu@=?5T$LpFpQ-)sV*bP_I@$t&LCM$XD`|^ba^qpcz99G3sB%xPzvCW@U{0=J_ zRDzze_>n*S+M~ZxDDI{O>>;rvv|@o4^X=8)Dx!Piz%N^aFPT|pqP62$ngAQMwB$=S zXnnkSPiWEKNftep3#$9zOW3Xx#GN1ro2<=r8x1)Hwv2%0DihsH6V=n(t};Se)Bu&p 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md new file mode 100644 index 0000000..77dd9d9 --- /dev/null +++ b/README.md @@ -0,0 +1,196 @@ +# Horux Despachos + +Plataforma SaaS para despachos profesionales mexicanos. Gestión fiscal multi-RFC con roles jerárquicos (Owner/Supervisor/Auxiliar/Cliente), carteras de contribuyentes, y arquitectura BYO-DB. + +**Autor:** Carlos e Ivan (Horux 360) + +--- + +## Qué es + +Horux Despachos permite a despachos contables gestionar múltiples contribuyentes (RFCs) desde una sola cuenta. Cada contribuyente tiene su propia FIEL, CSD, y organización Facturapi. Los supervisores organizan contribuyentes en carteras y delegan trabajo a auxiliares. + +## Arquitectura + +``` +Monorepo (pnpm + Turborepo) +├── apps/api → Express + TypeScript (puerto 4000) +├── apps/web → Next.js 14 + App Router (puerto 3000) +└── packages/ + ├── core → Auth (JWT), email transport, crypto (AES-256-GCM) + ├── shared → Tipos, constantes, interfaces compartidas + ├── shared-ui → Componentes UI (Button, Card, Dialog, selectors, hooks) + └── vertical-contable → (scaffold) Lógica fiscal compartida +``` + +## Funcionalidades implementadas + +### Gestión de despachos +- Signup multi-paso (formulario → vertical → plan) +- Onboarding wizard (6 pasos) +- Planes: Trial (30 días), Business Control (BYO-DB), Business Cloud (Managed) + +### Contribuyentes (RFCs) +- CRUD de contribuyentes por despacho +- FIEL per contribuyente (almacenada en BD tenant, cifrada AES-256-GCM) +- Facturapi org per contribuyente (CSD independiente) +- Emisión de CFDI con contribuyente_id + +### Roles y autorización +- **Owner**: acceso total, actúa como supervisor implícito +- **Supervisor**: titular de RFCs, crea carteras, gestiona auxiliares +- **Auxiliar**: accede solo a RFCs en carteras asignadas +- **Cliente**: visor externo read-only de sus RFCs +- `getEntidadesVisibles()`: cascada de permisos automática + +### Carteras +- CRUD completo (crear, editar, eliminar) +- Asignar/remover contribuyentes +- Asignar/remover auxiliares +- Cascada: si supervisor pierde RFC → auxiliares pierden acceso + +### Pricing +- Catálogo de planes (Business Control $21,000/año, Business Cloud $15,000/año + $45/RFC/mes) +- Add-ons recurrentes con multi-preapproval MercadoPago +- Paquetes de timbres one-shot + +### Connector BYO-DB +- Provisioning de tunnel (Cloudflare Tunnel ready) +- Heartbeat cada 30s con status en UI +- getPool() refactorizado para decrypt de conexiones BYO + +### Admin global +- Dashboard cross-despacho (métricas, lista despachos, actividad) +- Impersonación con motivo obligatorio + audit log +- Audit log expuesto al owner del despacho + +### Métricas pre-calculadas +- Hot/cold: año actual on-the-fly, años pasados pre-calculados +- Invalidación dirigida por cambios retroactivos en CFDIs +- Tablas: metricas_mensuales, acumuladas_anuales, contraparte, invalidaciones + +## Stack técnico + +| Capa | Tecnología | +|------|------------| +| Frontend | Next.js 14, React 18, Tailwind, shadcn/ui, Zustand, React Query | +| Backend | Node.js 20+, Express 4, TypeScript 5, Prisma 5.22 | +| BD Central | PostgreSQL 16 (Prisma ORM) | +| BD Tenant | PostgreSQL 16 (pg Pool + SQL raw + 17 migraciones numeradas) | +| Auth | JWT (15min) + refresh (7d) + bcrypt + magic link ready | +| Pagos | MercadoPago (preapproval + webhooks) | +| Email | Nodemailer + SMTP | +| Facturación | Facturapi (cuenta maestra broker) | + +## Setup local + +```bash +# Requisitos: Node 20+, pnpm 9+, PostgreSQL 16+ +pnpm install +cp apps/api/.env.example apps/api/.env # rellenar con secrets reales +echo "NEXT_PUBLIC_API_URL=http://localhost:4000/api" > apps/web/.env.local +cd apps/api && npx prisma generate +cd apps/api && npx prisma migrate deploy +cd apps/api && pnpm db:seed +pnpm dev # API :4000 + Web :3000 +``` + +## Deploy a producción + +### Pre-requisitos del servidor +- Ubuntu 22.04+ (probado en 24.04) +- Node 20+, pnpm 9+ +- PostgreSQL 16+ (con extensión `pg_trgm` para búsquedas full-text) +- Nginx (proxy + SSL Let's Encrypt) +- PM2 (process manager) — `npm i -g pm2` +- Playwright dependencies para SAT scrapers (`npx playwright install-deps chromium`) + +### Procedimiento de deploy fresco + +```bash +# 1. Clonar +git clone https://github.com/Torch2196/Horux_despachos_NL.git /root/Horux +cd /root/Horux + +# 2. Configurar env vars +cp apps/api/.env.example apps/api/.env +nano apps/api/.env # rellenar con secrets +echo "NEXT_PUBLIC_API_URL=https://api.horuxfin.com/api" > apps/web/.env.local + +# 3. Instalar deps con versiones exactas +pnpm install --frozen-lockfile + +# 4. BD central — schema y catálogos +pnpm db:generate +cd apps/api && npx prisma migrate deploy && cd ../.. +pnpm db:seed + +# 5. BD tenant — migraciones de cada tenant existente (idempotente) +pnpm --filter @horux/api db:migrate-tenants + +# 6. Tenant admin global (Horux 360 — RFC HTS240708LJA) +pnpm --filter @horux/api bootstrap:admin-global +# ↑ imprime password temporal del admin en consola — guardarla + +# 7. Lista negra del SAT (opcional, ~1MB de RFCs) +pnpm --filter @horux/api import:lista-negra + +# 8. Build de producción +pnpm build + +# 9. Configurar Nginx + SSL +sudo cp deploy/nginx/horux360.conf /etc/nginx/sites-available/ +sudo ln -s /etc/nginx/sites-available/horux360.conf /etc/nginx/sites-enabled/ +sudo certbot --nginx -d horuxfin.com -d api.horuxfin.com +sudo systemctl reload nginx + +# 10. Levantar con PM2 +pm2 start ecosystem.config.js +pm2 save +pm2 startup # autostart al boot +``` + +### Updates posteriores + +```bash +cd /root/Horux +git pull +pnpm install --frozen-lockfile +pnpm db:generate +cd apps/api && npx prisma migrate deploy && cd ../.. +pnpm --filter @horux/api db:migrate-tenants # si hay nuevas migraciones tenant +pnpm build +pm2 restart all +``` + +### Crons en producción + +Los crons internos arrancan automáticamente en `NODE_ENV=production`: +- **SAT sync diario** — 03:00 AM (todos los planes) +- **SAT incremental** — 11:00, 15:00, 19:00 (solo Enterprise) +- **Subscription lifecycle** — 02:30 AM (apply pending changes, expire trials, purge) +- **Reporte semanal email** — Lunes 08:00 AM +- **Opinión cumplimiento** — Domingos 04:00 AM +- **CSF mensual** — Día 1 cada mes 04:00 AM +- **SAT retry cron** — cada hora (reintentos jobs `pending`) +- **Watchdog jobs SAT** — cada 2h (mata jobs zombies) + +Si necesitás simular crons en dev: `ENABLE_CRONS_IN_DEV=1` en `apps/api/.env`. + +## Estructura de BD + +### BD Central (Prisma) +Tenant, User, TenantMembership, Rol, Subscription, SubscriptionAddon, Payment, PlanCatalogo, PlanAddonCatalogo, FielCredential, ConnectorHeartbeat, AuditLog, TimbreSuscripcion, TimbrePaquete, catálogos SAT. + +### BD Tenant (SQL migrations 001-017) +001-005: Schema base (rfcs, cfdis, conciliaciones, alertas, opiniones, declaraciones, constancias) +006: tenant_migrations tracking +007-009: Core (entidades_gestionadas, carteras, cliente_accesos) +010-013: Vertical contable (contribuyentes, fiel_contribuyente, facturapi_orgs, cfdi contribuyente_id) +014-017: Métricas (mensuales, acumuladas, contraparte, invalidaciones) + +## Autor + +**Carlos e Ivan (Horux 360)** + +--- diff --git a/apps/api/.env.example b/apps/api/.env.example new file mode 100644 index 0000000..e98e866 --- /dev/null +++ b/apps/api/.env.example @@ -0,0 +1,82 @@ +# ============================================================================= +# Horux 360 — API .env template +# ============================================================================= +# Copiá este archivo a `.env` en producción y rellená cada variable. +# Las marcadas REQUIRED son obligatorias — la app no arranca sin ellas (Zod). +# Las opcionales pueden quedar comentadas; sus features se desactivan en runtime. +# +# Validación: apps/api/src/config/env.ts +# ============================================================================= + +# ----- Runtime --------------------------------------------------------------- +NODE_ENV=production # development | production | test +PORT=4000 # default 4000 + +# ----- BD central (Prisma) — REQUIRED ---------------------------------------- +DATABASE_URL=postgresql://user:password@localhost:5432/horux360 + +# ----- JWT — REQUIRED -------------------------------------------------------- +# Generar con: `openssl rand -hex 64` +JWT_SECRET= # min 32 chars +JWT_EXPIRES_IN=15m # access token TTL +JWT_REFRESH_EXPIRES_IN=7d # refresh token TTL + +# ----- CORS / URLs ----------------------------------------------------------- +CORS_ORIGIN=https://horuxfin.com # comma-separated si son varios +FRONTEND_URL=https://horuxfin.com # usado por MP back_url, emails, etc. + +# ----- FIEL (cifrado de credenciales SAT) — REQUIRED ------------------------- +# Generar con: `openssl rand -hex 64` (DISTINTA al JWT_SECRET — rotación independiente) +FIEL_ENCRYPTION_KEY= # min 32 chars +FIEL_STORAGE_PATH=/var/horux/fiel # path donde se guardan archivos FIEL temporales + +# ----- MercadoPago (suscripciones self-serve) -------------------------------- +MP_ACCESS_TOKEN= # producción: APP_USR-... +MP_ACCESS_TOKEN_SANDBOX= # opcional: TEST-... para dev local sin cobro +MP_USE_SANDBOX=false # true → usa MP_ACCESS_TOKEN_SANDBOX +MP_WEBHOOK_SECRET= # firma HMAC del webhook MP (Settings → Notifs) +MP_NOTIFICATION_URL=https://api.horuxfin.com/api/webhooks/mercadopago +# Solo dev/staging — override del payer_email cuando el owner = collector. Vacío en prod. +MP_TEST_PAYER_EMAIL= + +# ----- SMTP (Nodemailer) — opcional pero recomendado ------------------------- +SMTP_HOST=smtp.gmail.com # default +SMTP_PORT=587 # default +SMTP_USER= # cuenta Gmail Workspace +SMTP_PASS= # app password (NO la password de la cuenta) +SMTP_FROM=Horux360 + +# ----- Notificaciones admin -------------------------------------------------- +ADMIN_EMAIL=carlos@horuxfin.com # destino de "nuevo cliente" + alertas internas + +# ----- Facturapi (emisión CFDI) — opcional ----------------------------------- +# Sin esto, los tenants no pueden emitir facturas, pero la app arranca. +FACTURAPI_USER_KEY= # sk_user_... (cuenta maestra Horux 360) + +# ----- Cloudflare Tunnel (BYO-DB connector) — opcional ----------------------- +CLOUDFLARE_API_TOKEN= +CLOUDFLARE_ACCOUNT_ID= +CLOUDFLARE_TUNNEL_DOMAIN=tunnel.horux.mx + +# ----- KMS para cifrar conexiones BYO-DB y tokens connector ------------------ +CONNECTOR_ENCRYPTION_KEY= # generar con `openssl rand -hex 64` + +# ----- Metabase (auto-registro BDs tenant para BI) — opcional ---------------- +# Sin METABASE_PASSWORD/PG_PASSWORD el service skipea silenciosamente. +METABASE_URL= +METABASE_USERNAME= +METABASE_PASSWORD= +METABASE_PG_HOST= +METABASE_PG_PORT= +METABASE_PG_USER= +METABASE_PG_PASSWORD= + +# ----- Cron control en dev (opcional) ---------------------------------------- +# ENABLE_CRONS_IN_DEV=1 # activa SAT sync, weekly emails, etc. en NODE_ENV=development + +# ----- Watchdog SAT thresholds (opcional, defaults razonables) --------------- +# STALE_PENDING_HOURS=12 # marca pending como failed si nextRetryAt > N h atrás +# STALE_RUNNING_HOURS=4 # marca running como failed si startedAt > N h atrás + +# ----- SAT Playwright headless toggle (debug temporal) ---------------------- +# SAT_HEADLESS=false # solo dev — muestra browser para debug de scrapers diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..118b8c4 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,65 @@ +{ + "name": "@horux/api", + "version": "0.0.1", + "private": true, + "author": "Carlos e Ivan (Horux 360)", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "lint": "eslint src/", + "typecheck": "tsc --noEmit", + "db:generate": "prisma generate", + "db:push": "prisma db push", + "db:migrate": "prisma migrate dev", + "db:seed": "tsx prisma/seed.ts", + "import:lista-negra": "tsx scripts/import-lista-negra.ts", + "db:migrate-tenants": "tsx scripts/migrate-tenants.ts", + "bootstrap:admin-global": "tsx scripts/bootstrap-horux360-admin.ts", + "legal:sync": "node scripts/extract-terminos.mjs", + "email:preview": "tsx scripts/preview-emails.mjs" + }, + "dependencies": { + "@horux/core": "workspace:*", + "@horux/shared": "workspace:*", + "@nodecfdi/cfdi-core": "^1.0.1", + "@nodecfdi/credentials": "^3.2.0", + "@nodecfdi/sat-ws-descarga-masiva": "^2.0.0", + "@prisma/client": "^5.22.0", + "adm-zip": "^0.5.16", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "exceljs": "^4.4.0", + "express": "^4.21.0", + "facturapi": "^4.14.2", + "fast-xml-parser": "^5.3.3", + "helmet": "^8.0.0", + "jsonwebtoken": "^9.0.2", + "mercadopago": "^2.12.0", + "node-cron": "^4.2.1", + "node-forge": "^1.3.3", + "nodemailer": "^8.0.2", + "pdf-parse": "^2.4.5", + "pg": "^8.18.0", + "playwright": "^1.59.1", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/adm-zip": "^0.5.7", + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jsonwebtoken": "^9.0.7", + "@types/node": "^22.0.0", + "@types/node-cron": "^3.0.11", + "@types/node-forge": "^1.3.14", + "@types/nodemailer": "^7.0.11", + "@types/pg": "^8.18.0", + "express-rate-limit": "^8.3.1", + "prisma": "^5.22.0", + "sql.js": "^1.14.1", + "tsx": "^4.19.0", + "typescript": "^5.3.0" + } +} diff --git a/apps/api/prisma/catalogos-sat-data.ts b/apps/api/prisma/catalogos-sat-data.ts new file mode 100644 index 0000000..40877cb --- /dev/null +++ b/apps/api/prisma/catalogos-sat-data.ts @@ -0,0 +1,121 @@ +// Catálogos SAT CFDI 4.0 para facturación + +export const FORMAS_PAGO = [ + { clave: '01', descripcion: 'Efectivo' }, + { clave: '02', descripcion: 'Cheque nominativo' }, + { clave: '03', descripcion: 'Transferencia electrónica de fondos' }, + { clave: '04', descripcion: 'Tarjeta de crédito' }, + { clave: '05', descripcion: 'Monedero electrónico' }, + { clave: '06', descripcion: 'Dinero electrónico' }, + { clave: '08', descripcion: 'Vales de despensa' }, + { clave: '12', descripcion: 'Dación en pago' }, + { clave: '13', descripcion: 'Pago por subrogación' }, + { clave: '14', descripcion: 'Pago por consignación' }, + { clave: '15', descripcion: 'Condonación' }, + { clave: '17', descripcion: 'Compensación' }, + { clave: '23', descripcion: 'Novación' }, + { clave: '24', descripcion: 'Confusión' }, + { clave: '25', descripcion: 'Remisión de deuda' }, + { clave: '26', descripcion: 'Prescripción o caducidad' }, + { clave: '27', descripcion: 'A satisfacción del acreedor' }, + { clave: '28', descripcion: 'Tarjeta de débito' }, + { clave: '29', descripcion: 'Tarjeta de servicios' }, + { clave: '30', descripcion: 'Aplicación de anticipos' }, + { clave: '31', descripcion: 'Intermediario pagos' }, + { clave: '99', descripcion: 'Por definir' }, +]; + +export const METODOS_PAGO = [ + { clave: 'PUE', descripcion: 'Pago en una sola exhibición' }, + { clave: 'PPD', descripcion: 'Pago en parcialidades o diferido' }, +]; + +export const USOS_CFDI = [ + { clave: 'G01', descripcion: 'Adquisición de mercancías', personaFisica: true, personaMoral: true }, + { clave: 'G02', descripcion: 'Devoluciones, descuentos o bonificaciones', personaFisica: true, personaMoral: true }, + { clave: 'G03', descripcion: 'Gastos en general', personaFisica: true, personaMoral: true }, + { clave: 'I01', descripcion: 'Construcciones', personaFisica: true, personaMoral: true }, + { clave: 'I02', descripcion: 'Mobiliario y equipo de oficina por inversiones', personaFisica: true, personaMoral: true }, + { clave: 'I03', descripcion: 'Equipo de transporte', personaFisica: true, personaMoral: true }, + { clave: 'I04', descripcion: 'Equipo de cómputo y accesorios', personaFisica: true, personaMoral: true }, + { clave: 'I05', descripcion: 'Dados, troqueles, moldes, matrices y herramental', personaFisica: true, personaMoral: true }, + { clave: 'I06', descripcion: 'Comunicaciones telefónicas', personaFisica: true, personaMoral: true }, + { clave: 'I07', descripcion: 'Comunicaciones satelitales', personaFisica: true, personaMoral: true }, + { clave: 'I08', descripcion: 'Otra maquinaria y equipo', personaFisica: true, personaMoral: true }, + { clave: 'D01', descripcion: 'Honorarios médicos, dentales y gastos hospitalarios', personaFisica: true, personaMoral: false }, + { clave: 'D02', descripcion: 'Gastos médicos por incapacidad o discapacidad', personaFisica: true, personaMoral: false }, + { clave: 'D03', descripcion: 'Gastos funerales', personaFisica: true, personaMoral: false }, + { clave: 'D04', descripcion: 'Donativos', personaFisica: true, personaMoral: true }, + { clave: 'D05', descripcion: 'Intereses reales efectivamente pagados por créditos hipotecarios', personaFisica: true, personaMoral: false }, + { clave: 'D06', descripcion: 'Aportaciones voluntarias al SAR', personaFisica: true, personaMoral: false }, + { clave: 'D07', descripcion: 'Primas por seguros de gastos médicos', personaFisica: true, personaMoral: false }, + { clave: 'D08', descripcion: 'Gastos de transportación escolar obligatoria', personaFisica: true, personaMoral: false }, + { clave: 'D09', descripcion: 'Depósitos en cuentas para el ahorro, primas de pensiones', personaFisica: true, personaMoral: false }, + { clave: 'D10', descripcion: 'Pagos por servicios educativos (colegiaturas)', personaFisica: true, personaMoral: false }, + { clave: 'S01', descripcion: 'Sin efectos fiscales', personaFisica: true, personaMoral: true }, + { clave: 'CP01', descripcion: 'Pagos', personaFisica: true, personaMoral: true }, + { clave: 'CN01', descripcion: 'Nómina', personaFisica: true, personaMoral: false }, +]; + +export const MONEDAS = [ + { clave: 'MXN', descripcion: 'Peso Mexicano', decimales: 2 }, + { clave: 'USD', descripcion: 'Dólar Americano', decimales: 2 }, + { clave: 'EUR', descripcion: 'Euro', decimales: 2 }, + { clave: 'GBP', descripcion: 'Libra Esterlina', decimales: 2 }, + { clave: 'CAD', descripcion: 'Dólar Canadiense', decimales: 2 }, + { clave: 'JPY', descripcion: 'Yen Japonés', decimales: 0 }, + { clave: 'XXX', descripcion: 'Los códigos asignados para transacciones en que intervenga ninguna moneda', decimales: 0 }, +]; + +export const CLAVES_UNIDAD = [ + { clave: 'H87', descripcion: 'Pieza' }, + { clave: 'E48', descripcion: 'Unidad de servicio' }, + { clave: 'KGM', descripcion: 'Kilogramo' }, + { clave: 'LTR', descripcion: 'Litro' }, + { clave: 'MTR', descripcion: 'Metro' }, + { clave: 'MTK', descripcion: 'Metro cuadrado' }, + { clave: 'MTQ', descripcion: 'Metro cúbico' }, + { clave: 'KWH', descripcion: 'Kilovatio hora' }, + { clave: 'TNE', descripcion: 'Tonelada' }, + { clave: 'GRM', descripcion: 'Gramo' }, + { clave: 'HUR', descripcion: 'Hora' }, + { clave: 'DAY', descripcion: 'Día' }, + { clave: 'MON', descripcion: 'Mes' }, + { clave: 'ANN', descripcion: 'Año' }, + { clave: 'XBX', descripcion: 'Caja' }, + { clave: 'XPK', descripcion: 'Paquete' }, + { clave: 'XKI', descripcion: 'Kit' }, + { clave: 'SET', descripcion: 'Conjunto' }, + { clave: 'XLT', descripcion: 'Lote' }, + { clave: 'ACT', descripcion: 'Actividad' }, + { clave: 'XUN', descripcion: 'Unidad' }, + { clave: 'DPC', descripcion: 'Docena de piezas' }, + { clave: 'XRO', descripcion: 'Rollo' }, + { clave: 'GLL', descripcion: 'Galón' }, + { clave: 'MLT', descripcion: 'Mililitro' }, + { clave: 'CMT', descripcion: 'Centímetro' }, +]; + +export const OBJETOS_IMP = [ + { clave: '01', descripcion: 'No objeto de impuesto' }, + { clave: '02', descripcion: 'Sí objeto de impuesto' }, + { clave: '03', descripcion: 'Sí objeto del impuesto y no obligado al desglose' }, + { clave: '04', descripcion: 'Sí objeto del impuesto y no causa impuesto' }, +]; + +export const TIPOS_RELACION = [ + { clave: '01', descripcion: 'Nota de crédito de los documentos relacionados' }, + { clave: '02', descripcion: 'Nota de débito de los documentos relacionados' }, + { clave: '03', descripcion: 'Devolución de mercancía sobre facturas o traslados previos' }, + { clave: '04', descripcion: 'Sustitución de los CFDI previos' }, + { clave: '05', descripcion: 'Traslados de mercancías facturados previamente' }, + { clave: '06', descripcion: 'Factura generada por los traslados previos' }, + { clave: '07', descripcion: 'CFDI por aplicación de anticipo' }, +]; + +export const EXPORTACIONES = [ + { clave: '01', descripcion: 'No aplica' }, + { clave: '02', descripcion: 'Definitiva' }, + { clave: '03', descripcion: 'Temporal' }, + { clave: '04', descripcion: 'Definitiva con clave distinta a A1 o cuando no existe enajenación en términos del CFF' }, +]; diff --git a/apps/api/prisma/eventos-fiscales-data.ts b/apps/api/prisma/eventos-fiscales-data.ts new file mode 100644 index 0000000..d92ee15 --- /dev/null +++ b/apps/api/prisma/eventos-fiscales-data.ts @@ -0,0 +1,185 @@ +// Catálogo de eventos fiscales +export const EVENTOS_FISCALES = [ + { + titulo: 'Declaración mensual ISR', + tipo: 'declaracion', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: false, + regimenes: 'todos', + condicion: null, + }, + { + titulo: 'Declaración mensual IVA', + tipo: 'declaracion', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: false, + regimenes: 'todos', + condicion: null, + }, + { + titulo: 'Declaración mensual IEPS', + tipo: 'declaracion', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: false, + regimenes: 'todos', + condicion: null, + }, + { + titulo: 'Declaración de sueldos y salarios', + tipo: 'declaracion', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: false, + regimenes: 'todos', + condicion: 'tiene_nomina', + }, + { + titulo: 'Pago provisional ISR', + tipo: 'pago', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: true, + regimenes: 'todos', + condicion: null, + }, + { + titulo: 'Pago provisional IVA', + tipo: 'pago', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: true, + regimenes: 'todos', + condicion: null, + }, + { + titulo: 'Pago provisional IEPS', + tipo: 'pago', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: true, + regimenes: 'todos', + condicion: null, + }, + { + titulo: 'DIOT', + tipo: 'obligacion', + diaBase: 17, + mesRelativo: 1, + recurrencia: 'mensual', + usaExtensionRfc: false, + regimenes: '601,603,607,608,610,611,612,614,615,620,622,623,624', + condicion: null, // 612 aplica condición ingresos_4m, se valida en runtime + }, + { + titulo: 'Contabilidad electrónica', + tipo: 'obligacion', + diaBase: 3, + mesRelativo: 2, + recurrencia: 'mensual', + usaExtensionRfc: false, + regimenes: '601,603,607,608,610,611,612,614,615,620,622,623,624', + condicion: null, + }, + { + titulo: 'Declaración anual PM', + tipo: 'declaracion', + diaBase: 31, + mesRelativo: 0, + mesFijo: 3, + recurrencia: 'anual', + usaExtensionRfc: false, + regimenes: '601,603,620,622,623,624', + condicion: null, + }, + { + titulo: 'Declaración anual PF', + tipo: 'declaracion', + diaBase: 30, + mesRelativo: 0, + mesFijo: 4, + recurrencia: 'anual', + usaExtensionRfc: false, + regimenes: '605,606,607,608,611,612,614,615,621,625,626', + condicion: null, + }, + { + titulo: 'Informativa Sueldos y Salarios', + tipo: 'informativa', + diaBase: 15, + mesRelativo: 0, + mesFijo: 2, + recurrencia: 'anual', + usaExtensionRfc: false, + regimenes: 'todos', + condicion: 'tiene_nomina', + }, +]; + +// Días festivos oficiales de México (2020-2027) +// Incluye: 1 ene, 5 feb, 21 mar, 1 may, 16 sep, 1 oct (cambio poder), 20 nov, 25 dic +// + cambios de poder cada 6 años, semana santa variable +export const DIAS_INHABILES: { fecha: string; nombre: string }[] = []; + +function addFestivos(año: number) { + const fijos = [ + { mes: 1, dia: 1, nombre: 'Año Nuevo' }, + { mes: 5, dia: 1, nombre: 'Día del Trabajo' }, + { mes: 9, dia: 16, nombre: 'Independencia de México' }, + { mes: 12, dia: 25, nombre: 'Navidad' }, + ]; + + // Primer lunes de febrero (Constitución) + const feb1 = new Date(año, 1, 1); + const primerLunesFeb = new Date(año, 1, 1 + ((8 - feb1.getDay()) % 7)); + DIAS_INHABILES.push({ + fecha: primerLunesFeb.toISOString().split('T')[0], + nombre: 'Día de la Constitución', + }); + + // Tercer lunes de marzo (Benito Juárez) + const mar1 = new Date(año, 2, 1); + const primerLunesMar = new Date(año, 2, 1 + ((8 - mar1.getDay()) % 7)); + const tercerLunesMar = new Date(primerLunesMar); + tercerLunesMar.setDate(tercerLunesMar.getDate() + 14); + DIAS_INHABILES.push({ + fecha: tercerLunesMar.toISOString().split('T')[0], + nombre: 'Natalicio de Benito Juárez', + }); + + // Tercer lunes de noviembre (Revolución) + const nov1 = new Date(año, 10, 1); + const primerLunesNov = new Date(año, 10, 1 + ((8 - nov1.getDay()) % 7)); + const tercerLunesNov = new Date(primerLunesNov); + tercerLunesNov.setDate(tercerLunesNov.getDate() + 14); + DIAS_INHABILES.push({ + fecha: tercerLunesNov.toISOString().split('T')[0], + nombre: 'Día de la Revolución', + }); + + for (const f of fijos) { + DIAS_INHABILES.push({ + fecha: `${año}-${String(f.mes).padStart(2, '0')}-${String(f.dia).padStart(2, '0')}`, + nombre: f.nombre, + }); + } + + // Cambio de poder (1 oct cada 6 años: 2024, 2030...) + if (año % 6 === 0 || (año - 2024) % 6 === 0) { + DIAS_INHABILES.push({ + fecha: `${año}-10-01`, + nombre: 'Transmisión del Poder Ejecutivo Federal', + }); + } +} + +for (let y = 2020; y <= 2027; y++) addFestivos(y); diff --git a/apps/api/prisma/isr-data.ts b/apps/api/prisma/isr-data.ts new file mode 100644 index 0000000..5585c49 --- /dev/null +++ b/apps/api/prisma/isr-data.ts @@ -0,0 +1,103 @@ +// Tasas RESICO (Art. 113-E) - iguales 2022-2026 +export const RESICO_TASAS = [ + { montoMaximo: 25000.00, porcentaje: 1.00 }, + { montoMaximo: 50000.00, porcentaje: 1.10 }, + { montoMaximo: 83888.33, porcentaje: 1.50 }, + { montoMaximo: 208333.33, porcentaje: 2.00 }, + { montoMaximo: 291666.66, porcentaje: 2.50 }, +]; + +// Tarifas ISR mensuales (Art. 96) por año +export const ISR_TARIFAS: Record = { + 2020: [ + { li: 0.01, ls: 578.52, cf: 0, pe: 1.92 }, + { li: 578.53, ls: 4910.18, cf: 11.11, pe: 6.40 }, + { li: 4910.19, ls: 8629.20, cf: 288.33, pe: 10.88 }, + { li: 8629.21, ls: 10031.07, cf: 692.96, pe: 16.00 }, + { li: 10031.08, ls: 12009.94, cf: 917.26, pe: 17.92 }, + { li: 12009.95, ls: 24222.31, cf: 1271.87, pe: 21.36 }, + { li: 24222.32, ls: 38177.69, cf: 3880.44, pe: 23.52 }, + { li: 38177.70, ls: 72887.50, cf: 7162.74, pe: 30.00 }, + { li: 72887.51, ls: 97183.33, cf: 17575.69, pe: 32.00 }, + { li: 97183.34, ls: 291550.00, cf: 25350.35, pe: 34.00 }, + { li: 291550.01, ls: null, cf: 91435.02, pe: 35.00 }, + ], + 2021: [ + { li: 0.01, ls: 644.58, cf: 0, pe: 1.92 }, + { li: 644.59, ls: 5470.92, cf: 12.38, pe: 6.40 }, + { li: 5470.93, ls: 9614.66, cf: 321.26, pe: 10.88 }, + { li: 9614.67, ls: 11176.62, cf: 772.10, pe: 16.00 }, + { li: 11176.63, ls: 13381.47, cf: 1022.01, pe: 17.92 }, + { li: 13381.48, ls: 26988.50, cf: 1417.12, pe: 21.36 }, + { li: 26988.51, ls: 42537.58, cf: 4323.58, pe: 23.52 }, + { li: 42537.59, ls: 81211.25, cf: 7980.73, pe: 30.00 }, + { li: 81211.26, ls: 108281.67, cf: 19582.83, pe: 32.00 }, + { li: 108281.68, ls: 324845.01, cf: 28245.36, pe: 34.00 }, + { li: 324845.02, ls: null, cf: 101876.90, pe: 35.00 }, + ], + 2022: [ + { li: 0.01, ls: 644.58, cf: 0, pe: 1.92 }, + { li: 644.59, ls: 5470.92, cf: 12.38, pe: 6.40 }, + { li: 5470.93, ls: 9614.66, cf: 321.26, pe: 10.88 }, + { li: 9614.67, ls: 11176.62, cf: 772.10, pe: 16.00 }, + { li: 11176.63, ls: 13381.47, cf: 1022.01, pe: 17.92 }, + { li: 13381.48, ls: 26988.50, cf: 1417.12, pe: 21.36 }, + { li: 26988.51, ls: 42537.58, cf: 4323.58, pe: 23.52 }, + { li: 42537.59, ls: 81211.25, cf: 7980.73, pe: 30.00 }, + { li: 81211.26, ls: 108281.67, cf: 19582.83, pe: 32.00 }, + { li: 108281.68, ls: 324845.01, cf: 28245.36, pe: 34.00 }, + { li: 324845.02, ls: null, cf: 101876.90, pe: 35.00 }, + ], + 2023: [ + { li: 0.01, ls: 746.04, cf: 0, pe: 1.92 }, + { li: 746.05, ls: 6332.05, cf: 14.32, pe: 6.40 }, + { li: 6332.06, ls: 11128.01, cf: 371.83, pe: 10.88 }, + { li: 11128.02, ls: 12935.82, cf: 893.63, pe: 16.00 }, + { li: 12935.83, ls: 15487.71, cf: 1182.88, pe: 17.92 }, + { li: 15487.72, ls: 31236.49, cf: 1640.18, pe: 21.36 }, + { li: 31236.50, ls: 49233.00, cf: 5004.12, pe: 23.52 }, + { li: 49233.01, ls: 93993.90, cf: 9236.89, pe: 30.00 }, + { li: 93993.91, ls: 125325.20, cf: 22665.17, pe: 32.00 }, + { li: 125325.21, ls: 375975.61, cf: 32691.18, pe: 34.00 }, + { li: 375975.62, ls: null, cf: 117912.32, pe: 35.00 }, + ], + 2024: [ + { li: 0.01, ls: 746.04, cf: 0, pe: 1.92 }, + { li: 746.05, ls: 6332.05, cf: 14.32, pe: 6.40 }, + { li: 6332.06, ls: 11128.01, cf: 371.83, pe: 10.88 }, + { li: 11128.02, ls: 12935.82, cf: 893.63, pe: 16.00 }, + { li: 12935.83, ls: 15487.71, cf: 1182.88, pe: 17.92 }, + { li: 15487.72, ls: 31236.49, cf: 1640.18, pe: 21.36 }, + { li: 31236.50, ls: 49233.00, cf: 5004.12, pe: 23.52 }, + { li: 49233.01, ls: 93993.90, cf: 9236.89, pe: 30.00 }, + { li: 93993.91, ls: 125325.20, cf: 22665.17, pe: 32.00 }, + { li: 125325.21, ls: 375975.61, cf: 32691.18, pe: 34.00 }, + { li: 375975.62, ls: null, cf: 117912.32, pe: 35.00 }, + ], + 2025: [ + { li: 0.01, ls: 746.04, cf: 0, pe: 1.92 }, + { li: 746.05, ls: 6332.05, cf: 14.32, pe: 6.40 }, + { li: 6332.06, ls: 11128.01, cf: 371.83, pe: 10.88 }, + { li: 11128.02, ls: 12935.82, cf: 893.63, pe: 16.00 }, + { li: 12935.83, ls: 15487.71, cf: 1182.88, pe: 17.92 }, + { li: 15487.72, ls: 31236.49, cf: 1640.18, pe: 21.36 }, + { li: 31236.50, ls: 49233.00, cf: 5004.12, pe: 23.52 }, + { li: 49233.01, ls: 93993.90, cf: 9236.89, pe: 30.00 }, + { li: 93993.91, ls: 125325.20, cf: 22665.17, pe: 32.00 }, + { li: 125325.21, ls: 375975.61, cf: 32691.18, pe: 34.00 }, + { li: 375975.62, ls: null, cf: 117912.32, pe: 35.00 }, + ], + 2026: [ + { li: 0.01, ls: 844.59, cf: 0, pe: 1.92 }, + { li: 844.60, ls: 7168.51, cf: 16.22, pe: 6.40 }, + { li: 7168.52, ls: 12598.02, cf: 420.95, pe: 10.88 }, + { li: 12598.03, ls: 14644.64, cf: 1011.68, pe: 16.00 }, + { li: 14644.65, ls: 17533.64, cf: 1339.14, pe: 17.92 }, + { li: 17533.65, ls: 35362.83, cf: 1856.84, pe: 21.36 }, + { li: 35362.84, ls: 55736.68, cf: 5665.16, pe: 23.52 }, + { li: 55736.69, ls: 106410.50, cf: 10457.09, pe: 30.00 }, + { li: 106410.51, ls: 141880.66, cf: 25659.23, pe: 32.00 }, + { li: 141880.67, ls: 425641.99, cf: 37009.69, pe: 34.00 }, + { li: 425642.00, ls: null, cf: 133488.54, pe: 35.00 }, + ], +}; diff --git a/apps/api/prisma/migrations/20260414152220_initial_schema_v0_9_2/migration.sql b/apps/api/prisma/migrations/20260414152220_initial_schema_v0_9_2/migration.sql new file mode 100644 index 0000000..17840b0 --- /dev/null +++ b/apps/api/prisma/migrations/20260414152220_initial_schema_v0_9_2/migration.sql @@ -0,0 +1,634 @@ +-- CreateEnum +CREATE TYPE "Plan" AS ENUM ('starter', 'business', 'business_ia', 'custom', 'enterprise'); + +-- CreateEnum +CREATE TYPE "PlatformRole" AS ENUM ('platform_admin', 'platform_ti', 'platform_support', 'platform_sales', 'platform_finance'); + +-- CreateEnum +CREATE TYPE "SatSyncType" AS ENUM ('initial', 'daily', 'incremental'); + +-- CreateEnum +CREATE TYPE "SatSyncStatus" AS ENUM ('pending', 'running', 'completed', 'failed'); + +-- CreateEnum +CREATE TYPE "CfdiSyncType" AS ENUM ('emitidos', 'recibidos'); + +-- CreateTable +CREATE TABLE "tenants" ( + "id" TEXT NOT NULL, + "nombre" TEXT NOT NULL, + "rfc" TEXT NOT NULL, + "plan" "Plan" NOT NULL DEFAULT 'starter', + "database_name" TEXT NOT NULL, + "cfdi_limit" INTEGER NOT NULL DEFAULT 100, + "users_limit" INTEGER NOT NULL DEFAULT 1, + "active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expires_at" TIMESTAMP(3), + "trial_ends_at" TIMESTAMP(3), + "facturapi_org_id" TEXT, + "codigo_postal" VARCHAR(5), + "calle" VARCHAR(255), + "num_exterior" VARCHAR(20), + "num_interior" VARCHAR(20), + "colonia" VARCHAR(255), + "ciudad" VARCHAR(100), + "municipio" VARCHAR(100), + "estado" VARCHAR(100), + "telefono" VARCHAR(20), + + CONSTRAINT "tenants_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password_hash" TEXT NOT NULL, + "nombre" TEXT NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "last_login" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "token_version" INTEGER NOT NULL DEFAULT 0, + "last_tenant_id" TEXT, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tenant_memberships" ( + "id" SERIAL NOT NULL, + "user_id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "rol_id" INTEGER NOT NULL, + "is_owner" BOOLEAN NOT NULL DEFAULT false, + "active" BOOLEAN NOT NULL DEFAULT true, + "joined_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "tenant_memberships_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "roles" ( + "id" SERIAL NOT NULL, + "nombre" VARCHAR(20) NOT NULL, + "descripcion" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "roles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "refresh_tokens" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expires_at" TIMESTAMP(3) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "refresh_tokens_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "password_reset_tokens" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expires_at" TIMESTAMP(3) NOT NULL, + "used_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "password_reset_tokens_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "regimenes" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(3) NOT NULL, + "descripcion" TEXT NOT NULL, + "tipo_persona" VARCHAR(20) NOT NULL, + "activo" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "regimenes_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tenant_regimenes_ignorados" ( + "id" SERIAL NOT NULL, + "tenant_id" TEXT NOT NULL, + "regimen_id" INTEGER NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "tenant_regimenes_ignorados_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tenant_regimenes_activos" ( + "id" SERIAL NOT NULL, + "tenant_id" TEXT NOT NULL, + "regimen_id" INTEGER NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "tenant_regimenes_activos_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "eventos_fiscales_catalogo" ( + "id" SERIAL NOT NULL, + "titulo" TEXT NOT NULL, + "descripcion" TEXT, + "tipo" VARCHAR(20) NOT NULL, + "dia_base" INTEGER NOT NULL, + "mes_relativo" INTEGER NOT NULL DEFAULT 1, + "mes_fijo" INTEGER, + "recurrencia" VARCHAR(20) NOT NULL DEFAULT 'mensual', + "usa_extension_rfc" BOOLEAN NOT NULL DEFAULT false, + "regimenes" TEXT NOT NULL DEFAULT 'todos', + "condicion" VARCHAR(50), + "activo" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "eventos_fiscales_catalogo_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "lista_negra" ( + "id" SERIAL NOT NULL, + "rfc" VARCHAR(13) NOT NULL, + "nombre" TEXT NOT NULL, + "situacion" VARCHAR(30) NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "lista_negra_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "dias_inhabiles" ( + "id" SERIAL NOT NULL, + "fecha" DATE NOT NULL, + "nombre" TEXT NOT NULL, + + CONSTRAINT "dias_inhabiles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "isr_resico_tasas" ( + "id" SERIAL NOT NULL, + "anio" INTEGER NOT NULL, + "monto_maximo" DECIMAL(18,2) NOT NULL, + "porcentaje" DECIMAL(5,2) NOT NULL, + + CONSTRAINT "isr_resico_tasas_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "isr_tarifas" ( + "id" SERIAL NOT NULL, + "anio" INTEGER NOT NULL, + "limite_inferior" DECIMAL(18,2) NOT NULL, + "limite_superior" DECIMAL(18,2), + "cuota_fija" DECIMAL(18,2) NOT NULL, + "porcentaje_excedente" DECIMAL(5,2) NOT NULL, + + CONSTRAINT "isr_tarifas_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "coeficiente_utilidad" ( + "id" SERIAL NOT NULL, + "tenant_id" TEXT NOT NULL, + "anio" INTEGER NOT NULL, + "coeficiente" DECIMAL(10,4) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "coeficiente_utilidad_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "fiel_credentials" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "rfc" VARCHAR(13) NOT NULL, + "cer_data" BYTEA NOT NULL, + "key_data" BYTEA NOT NULL, + "key_password_encrypted" BYTEA NOT NULL, + "cer_iv" BYTEA NOT NULL, + "cer_tag" BYTEA NOT NULL, + "key_iv" BYTEA NOT NULL, + "key_tag" BYTEA NOT NULL, + "password_iv" BYTEA NOT NULL, + "password_tag" BYTEA NOT NULL, + "serial_number" VARCHAR(50), + "valid_from" TIMESTAMP(3) NOT NULL, + "valid_until" TIMESTAMP(3) NOT NULL, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "fiel_credentials_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "subscriptions" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "plan" "Plan" NOT NULL, + "mp_preapproval_id" TEXT, + "status" TEXT NOT NULL DEFAULT 'pending', + "amount" DECIMAL(10,2) NOT NULL, + "frequency" TEXT NOT NULL DEFAULT 'monthly', + "current_period_start" TIMESTAMP(3), + "current_period_end" TIMESTAMP(3), + "pending_plan" "Plan", + "pending_frequency" TEXT, + "pending_effective_at" TIMESTAMP(3), + "upgrade_preference_id" TEXT, + "upgrade_target_plan" "Plan", + "upgrade_target_amount" DECIMAL(10,2), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "user_platform_roles" ( + "id" SERIAL NOT NULL, + "user_id" TEXT NOT NULL, + "role" "PlatformRole" NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT, + + CONSTRAINT "user_platform_roles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "audit_log" ( + "id" TEXT NOT NULL, + "user_id" TEXT, + "tenant_id" TEXT, + "action" VARCHAR(64) NOT NULL, + "entity_type" VARCHAR(32), + "entity_id" TEXT, + "metadata" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "audit_log_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "trial_usages" ( + "id" SERIAL NOT NULL, + "rfc" VARCHAR(13) NOT NULL, + "tenant_id" TEXT, + "started_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "trial_usages_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "plan_prices" ( + "id" SERIAL NOT NULL, + "plan" "Plan" NOT NULL, + "frequency" TEXT NOT NULL, + "amount" DECIMAL(10,2) NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "plan_prices_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "payments" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "subscription_id" TEXT, + "mp_payment_id" TEXT, + "amount" DECIMAL(10,2) NOT NULL, + "status" TEXT NOT NULL DEFAULT 'pending', + "payment_method" TEXT, + "paid_at" TIMESTAMP(3), + "facturapi_invoice_id" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "payments_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sat_sync_jobs" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "type" "SatSyncType" NOT NULL, + "status" "SatSyncStatus" NOT NULL DEFAULT 'pending', + "date_from" DATE NOT NULL, + "date_to" DATE NOT NULL, + "cfdi_type" "CfdiSyncType", + "sat_request_id" VARCHAR(50), + "sat_package_ids" TEXT[], + "cfdis_found" INTEGER NOT NULL DEFAULT 0, + "cfdis_downloaded" INTEGER NOT NULL DEFAULT 0, + "cfdis_inserted" INTEGER NOT NULL DEFAULT 0, + "cfdis_updated" INTEGER NOT NULL DEFAULT 0, + "progress_percent" INTEGER NOT NULL DEFAULT 0, + "error_message" TEXT, + "started_at" TIMESTAMP(3), + "completed_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "retry_count" INTEGER NOT NULL DEFAULT 0, + "next_retry_at" TIMESTAMP(3), + + CONSTRAINT "sat_sync_jobs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_forma_pago" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(2) NOT NULL, + "descripcion" TEXT NOT NULL, + + CONSTRAINT "cat_forma_pago_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_metodo_pago" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(3) NOT NULL, + "descripcion" TEXT NOT NULL, + + CONSTRAINT "cat_metodo_pago_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_uso_cfdi" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(4) NOT NULL, + "descripcion" TEXT NOT NULL, + "persona_fisica" BOOLEAN NOT NULL DEFAULT true, + "persona_moral" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "cat_uso_cfdi_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_moneda" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(3) NOT NULL, + "descripcion" TEXT NOT NULL, + "decimales" INTEGER NOT NULL DEFAULT 2, + + CONSTRAINT "cat_moneda_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_clave_unidad" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(10) NOT NULL, + "descripcion" TEXT NOT NULL, + + CONSTRAINT "cat_clave_unidad_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_clave_prod_serv" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(8) NOT NULL, + "descripcion" TEXT NOT NULL, + + CONSTRAINT "cat_clave_prod_serv_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_objeto_imp" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(2) NOT NULL, + "descripcion" TEXT NOT NULL, + + CONSTRAINT "cat_objeto_imp_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_tipo_relacion" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(2) NOT NULL, + "descripcion" TEXT NOT NULL, + + CONSTRAINT "cat_tipo_relacion_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "cat_exportacion" ( + "id" SERIAL NOT NULL, + "clave" VARCHAR(2) NOT NULL, + "descripcion" TEXT NOT NULL, + + CONSTRAINT "cat_exportacion_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "timbre_suscripciones" ( + "id" SERIAL NOT NULL, + "tenant_id" TEXT NOT NULL, + "tipo" VARCHAR(10) NOT NULL, + "timbres_limite" INTEGER NOT NULL, + "timbres_usados" INTEGER NOT NULL DEFAULT 0, + "periodo_inicio" DATE NOT NULL, + "periodo_fin" DATE NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "timbre_suscripciones_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "tenants_rfc_key" ON "tenants"("rfc"); + +-- CreateIndex +CREATE UNIQUE INDEX "tenants_database_name_key" ON "tenants"("database_name"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE INDEX "tenant_memberships_user_id_active_idx" ON "tenant_memberships"("user_id", "active"); + +-- CreateIndex +CREATE INDEX "tenant_memberships_tenant_id_active_idx" ON "tenant_memberships"("tenant_id", "active"); + +-- CreateIndex +CREATE UNIQUE INDEX "tenant_memberships_user_id_tenant_id_key" ON "tenant_memberships"("user_id", "tenant_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "roles_nombre_key" ON "roles"("nombre"); + +-- CreateIndex +CREATE UNIQUE INDEX "refresh_tokens_token_key" ON "refresh_tokens"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "password_reset_tokens_token_key" ON "password_reset_tokens"("token"); + +-- CreateIndex +CREATE INDEX "password_reset_tokens_user_id_idx" ON "password_reset_tokens"("user_id"); + +-- CreateIndex +CREATE INDEX "password_reset_tokens_expires_at_idx" ON "password_reset_tokens"("expires_at"); + +-- CreateIndex +CREATE UNIQUE INDEX "regimenes_clave_key" ON "regimenes"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "tenant_regimenes_ignorados_tenant_id_regimen_id_key" ON "tenant_regimenes_ignorados"("tenant_id", "regimen_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "tenant_regimenes_activos_tenant_id_regimen_id_key" ON "tenant_regimenes_activos"("tenant_id", "regimen_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "lista_negra_rfc_key" ON "lista_negra"("rfc"); + +-- CreateIndex +CREATE INDEX "lista_negra_rfc_idx" ON "lista_negra"("rfc"); + +-- CreateIndex +CREATE UNIQUE INDEX "dias_inhabiles_fecha_key" ON "dias_inhabiles"("fecha"); + +-- CreateIndex +CREATE UNIQUE INDEX "isr_resico_tasas_anio_monto_maximo_key" ON "isr_resico_tasas"("anio", "monto_maximo"); + +-- CreateIndex +CREATE UNIQUE INDEX "isr_tarifas_anio_limite_inferior_key" ON "isr_tarifas"("anio", "limite_inferior"); + +-- CreateIndex +CREATE UNIQUE INDEX "coeficiente_utilidad_tenant_id_anio_key" ON "coeficiente_utilidad"("tenant_id", "anio"); + +-- CreateIndex +CREATE UNIQUE INDEX "fiel_credentials_tenant_id_key" ON "fiel_credentials"("tenant_id"); + +-- CreateIndex +CREATE INDEX "subscriptions_tenant_id_idx" ON "subscriptions"("tenant_id"); + +-- CreateIndex +CREATE INDEX "subscriptions_status_idx" ON "subscriptions"("status"); + +-- CreateIndex +CREATE INDEX "subscriptions_pending_effective_at_idx" ON "subscriptions"("pending_effective_at"); + +-- CreateIndex +CREATE INDEX "user_platform_roles_role_idx" ON "user_platform_roles"("role"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_platform_roles_user_id_role_key" ON "user_platform_roles"("user_id", "role"); + +-- CreateIndex +CREATE INDEX "audit_log_user_id_created_at_idx" ON "audit_log"("user_id", "created_at"); + +-- CreateIndex +CREATE INDEX "audit_log_tenant_id_created_at_idx" ON "audit_log"("tenant_id", "created_at"); + +-- CreateIndex +CREATE INDEX "audit_log_action_created_at_idx" ON "audit_log"("action", "created_at"); + +-- CreateIndex +CREATE INDEX "audit_log_entity_type_entity_id_idx" ON "audit_log"("entity_type", "entity_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "trial_usages_rfc_key" ON "trial_usages"("rfc"); + +-- CreateIndex +CREATE UNIQUE INDEX "plan_prices_plan_frequency_key" ON "plan_prices"("plan", "frequency"); + +-- CreateIndex +CREATE INDEX "payments_tenant_id_idx" ON "payments"("tenant_id"); + +-- CreateIndex +CREATE INDEX "payments_subscription_id_idx" ON "payments"("subscription_id"); + +-- CreateIndex +CREATE INDEX "sat_sync_jobs_tenant_id_idx" ON "sat_sync_jobs"("tenant_id"); + +-- CreateIndex +CREATE INDEX "sat_sync_jobs_status_idx" ON "sat_sync_jobs"("status"); + +-- CreateIndex +CREATE INDEX "sat_sync_jobs_status_next_retry_at_idx" ON "sat_sync_jobs"("status", "next_retry_at"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_forma_pago_clave_key" ON "cat_forma_pago"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_metodo_pago_clave_key" ON "cat_metodo_pago"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_uso_cfdi_clave_key" ON "cat_uso_cfdi"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_moneda_clave_key" ON "cat_moneda"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_clave_unidad_clave_key" ON "cat_clave_unidad"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_clave_prod_serv_clave_key" ON "cat_clave_prod_serv"("clave"); + +-- CreateIndex +CREATE INDEX "cat_clave_prod_serv_descripcion_idx" ON "cat_clave_prod_serv"("descripcion"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_objeto_imp_clave_key" ON "cat_objeto_imp"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_tipo_relacion_clave_key" ON "cat_tipo_relacion"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "cat_exportacion_clave_key" ON "cat_exportacion"("clave"); + +-- CreateIndex +CREATE UNIQUE INDEX "timbre_suscripciones_tenant_id_key" ON "timbre_suscripciones"("tenant_id"); + +-- AddForeignKey +ALTER TABLE "tenant_memberships" ADD CONSTRAINT "tenant_memberships_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tenant_memberships" ADD CONSTRAINT "tenant_memberships_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tenant_memberships" ADD CONSTRAINT "tenant_memberships_rol_id_fkey" FOREIGN KEY ("rol_id") REFERENCES "roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tenant_regimenes_ignorados" ADD CONSTRAINT "tenant_regimenes_ignorados_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tenant_regimenes_ignorados" ADD CONSTRAINT "tenant_regimenes_ignorados_regimen_id_fkey" FOREIGN KEY ("regimen_id") REFERENCES "regimenes"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tenant_regimenes_activos" ADD CONSTRAINT "tenant_regimenes_activos_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tenant_regimenes_activos" ADD CONSTRAINT "tenant_regimenes_activos_regimen_id_fkey" FOREIGN KEY ("regimen_id") REFERENCES "regimenes"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "coeficiente_utilidad" ADD CONSTRAINT "coeficiente_utilidad_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "fiel_credentials" ADD CONSTRAINT "fiel_credentials_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_platform_roles" ADD CONSTRAINT "user_platform_roles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "payments" ADD CONSTRAINT "payments_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "payments" ADD CONSTRAINT "payments_subscription_id_fkey" FOREIGN KEY ("subscription_id") REFERENCES "subscriptions"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "sat_sync_jobs" ADD CONSTRAINT "sat_sync_jobs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "timbre_suscripciones" ADD CONSTRAINT "timbre_suscripciones_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + diff --git a/apps/api/prisma/migrations/20260415000057_timbres_paquetes_adicionales/migration.sql b/apps/api/prisma/migrations/20260415000057_timbres_paquetes_adicionales/migration.sql new file mode 100644 index 0000000..932ed19 --- /dev/null +++ b/apps/api/prisma/migrations/20260415000057_timbres_paquetes_adicionales/migration.sql @@ -0,0 +1,47 @@ +-- CreateEnum +CREATE TYPE "PaymentKind" AS ENUM ('subscription', 'timbres_pack'); + +-- AlterTable +ALTER TABLE "payments" ADD COLUMN "kind" "PaymentKind" NOT NULL DEFAULT 'subscription'; + +-- CreateTable +CREATE TABLE "timbre_paquetes_catalogo" ( + "id" SERIAL NOT NULL, + "cantidad" INTEGER NOT NULL, + "precio" DECIMAL(10,2) NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "timbre_paquetes_catalogo_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "timbre_paquetes" ( + "id" SERIAL NOT NULL, + "tenant_id" TEXT NOT NULL, + "payment_id" TEXT, + "cantidad" INTEGER NOT NULL, + "usados" INTEGER NOT NULL DEFAULT 0, + "precio" DECIMAL(10,2) NOT NULL, + "adquirido_en" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expira_en" TIMESTAMP(3) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "timbre_paquetes_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "timbre_paquetes_catalogo_cantidad_key" ON "timbre_paquetes_catalogo"("cantidad"); + +-- CreateIndex +CREATE UNIQUE INDEX "timbre_paquetes_payment_id_key" ON "timbre_paquetes"("payment_id"); + +-- CreateIndex +CREATE INDEX "timbre_paquetes_tenant_id_expira_en_idx" ON "timbre_paquetes"("tenant_id", "expira_en"); + +-- AddForeignKey +ALTER TABLE "timbre_paquetes" ADD CONSTRAINT "timbre_paquetes_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "timbre_paquetes" ADD CONSTRAINT "timbre_paquetes_payment_id_fkey" FOREIGN KEY ("payment_id") REFERENCES "payments"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20260417204528_despacho_fields/migration.sql b/apps/api/prisma/migrations/20260417204528_despacho_fields/migration.sql new file mode 100644 index 0000000..e7531e7 --- /dev/null +++ b/apps/api/prisma/migrations/20260417204528_despacho_fields/migration.sql @@ -0,0 +1,16 @@ +-- CreateEnum +CREATE TYPE "VerticalProfile" AS ENUM ('CONTABLE', 'JURIDICO', 'ARQUITECTURA'); + +-- CreateEnum +CREATE TYPE "DbMode" AS ENUM ('BYO', 'MANAGED'); + +-- AlterTable +ALTER TABLE "tenants" ADD COLUMN "connector_last_seen" TIMESTAMP(3), +ADD COLUMN "connector_token_enc" TEXT, +ADD COLUMN "connector_tunnel_hostname" TEXT, +ADD COLUMN "connector_version" VARCHAR(20), +ADD COLUMN "db_connection_enc" TEXT, +ADD COLUMN "db_connection_iv" TEXT, +ADD COLUMN "db_mode" "DbMode", +ADD COLUMN "db_schema_version" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "vertical_profile" "VerticalProfile"; diff --git a/apps/api/prisma/migrations/20260417224212_plan_catalogo_tables/migration.sql b/apps/api/prisma/migrations/20260417224212_plan_catalogo_tables/migration.sql new file mode 100644 index 0000000..8980a2e --- /dev/null +++ b/apps/api/prisma/migrations/20260417224212_plan_catalogo_tables/migration.sql @@ -0,0 +1,35 @@ +-- CreateTable +CREATE TABLE "plan_catalogo" ( + "id" TEXT NOT NULL, + "codename" VARCHAR(50) NOT NULL, + "nombre" TEXT NOT NULL, + "verticalProfile" "VerticalProfile" NOT NULL, + "precio_base" DECIMAL(10,2) NOT NULL, + "frecuencia" VARCHAR(10) NOT NULL, + "limits" JSONB NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "plan_catalogo_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "plan_addon_catalogo" ( + "id" TEXT NOT NULL, + "codename" VARCHAR(50) NOT NULL, + "nombre" TEXT NOT NULL, + "verticalProfile" "VerticalProfile", + "precio" DECIMAL(10,2) NOT NULL, + "frecuencia" VARCHAR(10) NOT NULL, + "delta" JSONB NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "plan_addon_catalogo_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "plan_catalogo_codename_key" ON "plan_catalogo"("codename"); + +-- CreateIndex +CREATE UNIQUE INDEX "plan_addon_catalogo_codename_key" ON "plan_addon_catalogo"("codename"); diff --git a/apps/api/prisma/migrations/20260417224614_subscription_addons/migration.sql b/apps/api/prisma/migrations/20260417224614_subscription_addons/migration.sql new file mode 100644 index 0000000..b5a6076 --- /dev/null +++ b/apps/api/prisma/migrations/20260417224614_subscription_addons/migration.sql @@ -0,0 +1,28 @@ +-- CreateTable +CREATE TABLE "subscription_addons" ( + "id" TEXT NOT NULL, + "subscription_id" TEXT NOT NULL, + "plan_addon_catalogo_id" TEXT NOT NULL, + "mp_preapproval_id" TEXT, + "status" TEXT NOT NULL DEFAULT 'pending', + "quantity" INTEGER NOT NULL DEFAULT 1, + "amount" DECIMAL(10,2) NOT NULL, + "current_period_start" TIMESTAMP(3), + "current_period_end" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "subscription_addons_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "subscription_addons_subscription_id_idx" ON "subscription_addons"("subscription_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "subscription_addons_subscription_id_plan_addon_catalogo_id_key" ON "subscription_addons"("subscription_id", "plan_addon_catalogo_id"); + +-- AddForeignKey +ALTER TABLE "subscription_addons" ADD CONSTRAINT "subscription_addons_subscription_id_fkey" FOREIGN KEY ("subscription_id") REFERENCES "subscriptions"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "subscription_addons" ADD CONSTRAINT "subscription_addons_plan_addon_catalogo_id_fkey" FOREIGN KEY ("plan_addon_catalogo_id") REFERENCES "plan_addon_catalogo"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20260417225702_connector_heartbeats/migration.sql b/apps/api/prisma/migrations/20260417225702_connector_heartbeats/migration.sql new file mode 100644 index 0000000..c23eba4 --- /dev/null +++ b/apps/api/prisma/migrations/20260417225702_connector_heartbeats/migration.sql @@ -0,0 +1,19 @@ +-- CreateTable +CREATE TABLE "connector_heartbeats" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "latency_ms" INTEGER NOT NULL, + "version" VARCHAR(20) NOT NULL, + "pg_version" VARCHAR(50), + "status" VARCHAR(20) NOT NULL, + "error_msg" TEXT, + + CONSTRAINT "connector_heartbeats_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "connector_heartbeats_tenant_id_timestamp_idx" ON "connector_heartbeats"("tenant_id", "timestamp"); + +-- AddForeignKey +ALTER TABLE "connector_heartbeats" ADD CONSTRAINT "connector_heartbeats_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20260418165004_sat_sync_contribuyente_id/migration.sql b/apps/api/prisma/migrations/20260418165004_sat_sync_contribuyente_id/migration.sql new file mode 100644 index 0000000..068873e --- /dev/null +++ b/apps/api/prisma/migrations/20260418165004_sat_sync_contribuyente_id/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "sat_sync_jobs" ADD COLUMN "contribuyente_id" TEXT; diff --git a/apps/api/prisma/migrations/20260421062505_despacho_plan_enum_values/migration.sql b/apps/api/prisma/migrations/20260421062505_despacho_plan_enum_values/migration.sql new file mode 100644 index 0000000..4ab5a7f --- /dev/null +++ b/apps/api/prisma/migrations/20260421062505_despacho_plan_enum_values/migration.sql @@ -0,0 +1,10 @@ +-- AlterEnum +-- This migration adds more than one value to an enum. +-- With PostgreSQL versions 11 and earlier, this is not possible +-- in a single migration. This can be worked around by creating +-- multiple migrations, each migration adding only one value to +-- the enum. + + +ALTER TYPE "Plan" ADD VALUE 'business_control'; +ALTER TYPE "Plan" ADD VALUE 'business_cloud'; diff --git a/apps/api/prisma/migrations/20260422172323_subscription_addons_contribuyente_id/migration.sql b/apps/api/prisma/migrations/20260422172323_subscription_addons_contribuyente_id/migration.sql new file mode 100644 index 0000000..c14d941 --- /dev/null +++ b/apps/api/prisma/migrations/20260422172323_subscription_addons_contribuyente_id/migration.sql @@ -0,0 +1,18 @@ +-- Add-ons por contribuyente: permite que SubscriptionAddon se asocie a un +-- contribuyente específico (ej. Lolita IA $250/mes activable por RFC) además +-- de los add-ons a nivel tenant (modulos, +RFCs, +timbres) que tienen +-- contribuyente_id = NULL. + +ALTER TABLE "subscription_addons" + ADD COLUMN "contribuyente_id" TEXT; + +-- Eliminar el UNIQUE (subscription_id, plan_addon_catalogo_id). Ahora el +-- mismo add-on (p. ej. lolita_ia_contribuyente) puede tener N filas por +-- subscription, una por cada contribuyente que lo contrate. +ALTER TABLE "subscription_addons" + DROP CONSTRAINT IF EXISTS "subscription_addons_subscription_id_plan_addon_catalogo_id_key"; + +-- Índice por (subscription_id, contribuyente_id) para lookups rápidos +-- "qué add-ons tiene este contribuyente" +CREATE INDEX IF NOT EXISTS "subscription_addons_subscription_id_contribuyente_id_idx" + ON "subscription_addons"("subscription_id", "contribuyente_id"); diff --git a/apps/api/prisma/migrations/20260426073942_add_mi_empresa_plan/migration.sql b/apps/api/prisma/migrations/20260426073942_add_mi_empresa_plan/migration.sql new file mode 100644 index 0000000..ed52500 --- /dev/null +++ b/apps/api/prisma/migrations/20260426073942_add_mi_empresa_plan/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "Plan" ADD VALUE 'mi_empresa'; diff --git a/apps/api/prisma/migrations/20260426230000_despacho_plan_prices/migration.sql b/apps/api/prisma/migrations/20260426230000_despacho_plan_prices/migration.sql new file mode 100644 index 0000000..a878322 --- /dev/null +++ b/apps/api/prisma/migrations/20260426230000_despacho_plan_prices/migration.sql @@ -0,0 +1,18 @@ +-- CreateTable +CREATE TABLE "despacho_plan_prices" ( + "plan" TEXT NOT NULL, + "monthly" DECIMAL(10,2), + "first_year" DECIMAL(10,2) NOT NULL, + "renewal" DECIMAL(10,2) NOT NULL, + "permite_monthly" BOOLEAN NOT NULL DEFAULT false, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "despacho_plan_prices_pkey" PRIMARY KEY ("plan") +); + +-- Seed inicial con valores actuales del catálogo `DESPACHO_PLAN_PRICES`. +INSERT INTO "despacho_plan_prices" ("plan", "monthly", "first_year", "renewal", "permite_monthly", "updated_at") VALUES + ('mi_empresa', 580, 5800, 5800, true, NOW()), + ('mi_empresa_plus', 900, 9000, 9000, true, NOW()), + ('business_control', NULL, 25850, 25850, false, NOW()), + ('business_cloud', NULL, 43000, 43000, false, NOW()); diff --git a/apps/api/prisma/migrations/20260430184123_cleanup_legacy_plans/migration.sql b/apps/api/prisma/migrations/20260430184123_cleanup_legacy_plans/migration.sql new file mode 100644 index 0000000..55620a2 --- /dev/null +++ b/apps/api/prisma/migrations/20260430184123_cleanup_legacy_plans/migration.sql @@ -0,0 +1,19 @@ +-- AlterEnum +BEGIN; +CREATE TYPE "Plan_new" AS ENUM ('trial', 'custom', 'business_control', 'business_cloud', 'mi_empresa', 'mi_empresa_plus'); +ALTER TABLE "tenants" ALTER COLUMN "plan" DROP DEFAULT; +ALTER TABLE "tenants" ALTER COLUMN "plan" TYPE "Plan_new" USING ("plan"::text::"Plan_new"); +ALTER TABLE "subscriptions" ALTER COLUMN "plan" TYPE "Plan_new" USING ("plan"::text::"Plan_new"); +ALTER TABLE "subscriptions" ALTER COLUMN "pending_plan" TYPE "Plan_new" USING ("pending_plan"::text::"Plan_new"); +ALTER TABLE "subscriptions" ALTER COLUMN "upgrade_target_plan" TYPE "Plan_new" USING ("upgrade_target_plan"::text::"Plan_new"); +ALTER TABLE "plan_prices" ALTER COLUMN "plan" TYPE "Plan_new" USING ("plan"::text::"Plan_new"); +ALTER TYPE "Plan" RENAME TO "Plan_old"; +ALTER TYPE "Plan_new" RENAME TO "Plan"; +DROP TYPE "Plan_old"; +ALTER TABLE "tenants" ALTER COLUMN "plan" SET DEFAULT 'trial'; +COMMIT; + +-- AlterTable +ALTER TABLE "tenants" DROP COLUMN "cfdi_limit", +DROP COLUMN "users_limit", +ALTER COLUMN "plan" SET DEFAULT 'trial'; diff --git a/apps/api/prisma/migrations/20260430195000_extend_despacho_plan_prices_with_limits/migration.sql b/apps/api/prisma/migrations/20260430195000_extend_despacho_plan_prices_with_limits/migration.sql new file mode 100644 index 0000000..f1adb66 --- /dev/null +++ b/apps/api/prisma/migrations/20260430195000_extend_despacho_plan_prices_with_limits/migration.sql @@ -0,0 +1,55 @@ +-- Step 1: Add new columns as nullable (preserva las 4 filas existentes con sus precios) +ALTER TABLE "despacho_plan_prices" + ADD COLUMN "nombre" VARCHAR(50), + ADD COLUMN "max_rfcs" INTEGER, + ADD COLUMN "max_users" INTEGER, + ADD COLUMN "db_mode" "DbMode", + ADD COLUMN "timbres_incluidos_mes" INTEGER NOT NULL DEFAULT 0, + ADD COLUMN "permite_servidor_backup" BOOLEAN NOT NULL DEFAULT false; + +-- Step 2: Backfill limits para las 4 filas existentes desde el catálogo TS +UPDATE "despacho_plan_prices" SET + "nombre" = 'Mi Empresa', + "max_rfcs" = 1, + "max_users" = 3, + "timbres_incluidos_mes" = 50, + "db_mode" = 'MANAGED' +WHERE "plan" = 'mi_empresa'; + +UPDATE "despacho_plan_prices" SET + "nombre" = 'Mi Empresa +', + "max_rfcs" = 1, + "max_users" = 3, + "timbres_incluidos_mes" = 50, + "db_mode" = 'MANAGED' +WHERE "plan" = 'mi_empresa_plus'; + +UPDATE "despacho_plan_prices" SET + "nombre" = 'Business Control', + "max_rfcs" = 100, + "max_users" = -1, + "timbres_incluidos_mes" = 0, + "db_mode" = 'BYO', + "permite_servidor_backup" = true +WHERE "plan" = 'business_control'; + +UPDATE "despacho_plan_prices" SET + "nombre" = 'Enterprise', + "max_rfcs" = 100, + "max_users" = -1, + "timbres_incluidos_mes" = 0, + "db_mode" = 'BYO', + "permite_servidor_backup" = true +WHERE "plan" = 'business_cloud'; + +-- Step 3: Set NOT NULL después del backfill (las 4 filas ya están completas) +ALTER TABLE "despacho_plan_prices" + ALTER COLUMN "nombre" SET NOT NULL, + ALTER COLUMN "max_rfcs" SET NOT NULL, + ALTER COLUMN "max_users" SET NOT NULL, + ALTER COLUMN "db_mode" SET NOT NULL; + +-- Step 4: Hacer firstYear y renewal nullable para soportar trial y custom (sin precio fijo) +ALTER TABLE "despacho_plan_prices" + ALTER COLUMN "first_year" DROP NOT NULL, + ALTER COLUMN "renewal" DROP NOT NULL; diff --git a/apps/api/prisma/migrations/20260430200000_drop_plan_catalogo_orphan/migration.sql b/apps/api/prisma/migrations/20260430200000_drop_plan_catalogo_orphan/migration.sql new file mode 100644 index 0000000..5245db2 --- /dev/null +++ b/apps/api/prisma/migrations/20260430200000_drop_plan_catalogo_orphan/migration.sql @@ -0,0 +1,5 @@ +-- Drop tabla plan_catalogo (modelo huérfano nunca usado por código activo). +-- Las 2 filas que tenía estaban desincronizadas con el catálogo TS y nunca +-- se referenciaron desde código real. El catálogo despacho vive ahora en +-- `despacho_plan_prices` (extendida con limits en migración 20260430195000). +DROP TABLE "plan_catalogo"; diff --git a/apps/api/prisma/migrations/20260430215000_add_permite_sat_incremental/migration.sql b/apps/api/prisma/migrations/20260430215000_add_permite_sat_incremental/migration.sql new file mode 100644 index 0000000..e49b890 --- /dev/null +++ b/apps/api/prisma/migrations/20260430215000_add_permite_sat_incremental/migration.sql @@ -0,0 +1,10 @@ +-- Add column with default false (no-op para filas existentes) +ALTER TABLE "despacho_plan_prices" + ADD COLUMN "permite_sat_incremental" BOOLEAN NOT NULL DEFAULT false; + +-- Backfill: planes que SÍ deben tener incremental (3 syncs/día adicionales). +-- Mi Empresa + tiene API + Lolita IA y precio premium ($9k anual); +-- Business Control y Enterprise son los planes despacho con escala alta. +UPDATE "despacho_plan_prices" +SET "permite_sat_incremental" = true +WHERE "plan" IN ('mi_empresa_plus', 'business_control', 'business_cloud'); diff --git a/apps/api/prisma/migrations/20260430230000_add_facturapi_org_key_enc/migration.sql b/apps/api/prisma/migrations/20260430230000_add_facturapi_org_key_enc/migration.sql new file mode 100644 index 0000000..a4268f3 --- /dev/null +++ b/apps/api/prisma/migrations/20260430230000_add_facturapi_org_key_enc/migration.sql @@ -0,0 +1,7 @@ +-- Cache cifrada de la Live Secret Key de la organización Facturapi del tenant +-- central (Horux 360 admin que emite facturas de subscripción a clientes). +-- AES-256-GCM con derivación FIEL_ENCRYPTION_KEY — mismo patrón que FIEL. +ALTER TABLE "tenants" + ADD COLUMN "facturapi_org_key_enc" BYTEA, + ADD COLUMN "facturapi_org_key_iv" BYTEA, + ADD COLUMN "facturapi_org_key_tag" BYTEA; diff --git a/apps/api/prisma/migrations/20260501160000_drop_plan_prices_legacy/migration.sql b/apps/api/prisma/migrations/20260501160000_drop_plan_prices_legacy/migration.sql new file mode 100644 index 0000000..e4e5f04 --- /dev/null +++ b/apps/api/prisma/migrations/20260501160000_drop_plan_prices_legacy/migration.sql @@ -0,0 +1,5 @@ +-- Drop tabla plan_prices (modelo legacy Horux 360 sin filas activas). +-- Catálogo se reemplazó por DespachoPlanPrice (despacho_plan_prices) en +-- migración 20260430195000_extend_despacho_plan_prices_with_limits. +-- Sin callers activos en código (verificado vía typecheck post-cleanup). +DROP TABLE "plan_prices"; diff --git a/apps/api/prisma/migrations/20260501170000_add_subscription_reminder_tracking/migration.sql b/apps/api/prisma/migrations/20260501170000_add_subscription_reminder_tracking/migration.sql new file mode 100644 index 0000000..3262193 --- /dev/null +++ b/apps/api/prisma/migrations/20260501170000_add_subscription_reminder_tracking/migration.sql @@ -0,0 +1,5 @@ +-- Tracking de aviso pre-vencimiento por suscripción. Permite que el cron diario +-- evite enviar dos emails del mismo bucket de días al mismo owner. +ALTER TABLE "subscriptions" + ADD COLUMN "last_reminder_day" INTEGER, + ADD COLUMN "last_reminder_sent_at" TIMESTAMP(3); diff --git a/apps/api/prisma/migrations/20260502170000_add_tenant_fact_preferencias/migration.sql b/apps/api/prisma/migrations/20260502170000_add_tenant_fact_preferencias/migration.sql new file mode 100644 index 0000000..701a751 --- /dev/null +++ b/apps/api/prisma/migrations/20260502170000_add_tenant_fact_preferencias/migration.sql @@ -0,0 +1,8 @@ +-- Preferencias de auto-facturación de pagos de suscripción. +-- factPreferencia: 'publico_general' o 'mis_datos' (default: mis_datos) +-- factUsoCfdi: clave SAT del uso CFDI default (G03 = Gastos en general) +-- factRegimenPreferido: clave del régimen fiscal a usar cuando hay multi-régimen +ALTER TABLE "tenants" + ADD COLUMN "fact_preferencia" VARCHAR(20) DEFAULT 'mis_datos' NOT NULL, + ADD COLUMN "fact_uso_cfdi" VARCHAR(5) DEFAULT 'G03' NOT NULL, + ADD COLUMN "fact_regimen_preferido" VARCHAR(3); diff --git a/apps/api/prisma/migrations/20260502190000_add_user_login_count_onboarding_dismissed/migration.sql b/apps/api/prisma/migrations/20260502190000_add_user_login_count_onboarding_dismissed/migration.sql new file mode 100644 index 0000000..98b1bab --- /dev/null +++ b/apps/api/prisma/migrations/20260502190000_add_user_login_count_onboarding_dismissed/migration.sql @@ -0,0 +1,4 @@ +-- Onboarding auto-dismiss: 4 logins ó pasos completados, lo que pase primero. +ALTER TABLE "users" + ADD COLUMN "login_count" INTEGER NOT NULL DEFAULT 0, + ADD COLUMN "onboarding_dismissed_at" TIMESTAMP(3); diff --git a/apps/api/prisma/migrations/20260502210000_add_sat_sync_jobs_request_ids_map/migration.sql b/apps/api/prisma/migrations/20260502210000_add_sat_sync_jobs_request_ids_map/migration.sql new file mode 100644 index 0000000..852e725 --- /dev/null +++ b/apps/api/prisma/migrations/20260502210000_add_sat_sync_jobs_request_ids_map/migration.sql @@ -0,0 +1,6 @@ +-- Mapa { kindKey: requestId } para reusar requests del SAT en reintentos. +-- Hasta antes de este cambio, cada retry creaba nuevas solicitudes — agotaba +-- la cuota del SAT y abandonaba requests anteriores. Ahora el retry consulta +-- los requestIds previos antes de crear nuevos. +ALTER TABLE "sat_sync_jobs" + ADD COLUMN "sat_request_ids" JSONB NOT NULL DEFAULT '{}'::jsonb; diff --git a/apps/api/prisma/migrations/20260502230000_add_sat_sync_jobs_is_custom_range/migration.sql b/apps/api/prisma/migrations/20260502230000_add_sat_sync_jobs_is_custom_range/migration.sql new file mode 100644 index 0000000..aaa3388 --- /dev/null +++ b/apps/api/prisma/migrations/20260502230000_add_sat_sync_jobs_is_custom_range/migration.sql @@ -0,0 +1,6 @@ +-- Distingue extracciones tipo `initial` con rango personalizado (UI custom) +-- de bootstrap inicial puro. Política de retry distinta: +-- initial bootstrap → 3 retries a 6h, 12h, 24h +-- initial custom → 2 retries a 6h, 12h +ALTER TABLE "sat_sync_jobs" + ADD COLUMN "is_custom_range" BOOLEAN NOT NULL DEFAULT false; diff --git a/apps/api/prisma/migrations/migration_lock.toml b/apps/api/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/apps/api/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma new file mode 100644 index 0000000..e199b29 --- /dev/null +++ b/apps/api/prisma/schema.prisma @@ -0,0 +1,760 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Tenant { + id String @id @default(uuid()) + nombre String + rfc String @unique + plan Plan @default(trial) + databaseName String @unique @map("database_name") + active Boolean @default(true) + createdAt DateTime @default(now()) @map("created_at") + expiresAt DateTime? @map("expires_at") + // Prueba gratuita: si está set y en el futuro, el tenant está en trial. + // Se consume una sola vez por tenant (al activarla, nunca se regenera). + trialEndsAt DateTime? @map("trial_ends_at") + + facturapiOrgId String? @map("facturapi_org_id") + /// Live Secret Key cifrada (AES-256-GCM, misma derivación FIEL_ENCRYPTION_KEY). + /// Cacheada tras primer PUT idempotente a /v2/organizations/{id}/apikeys/live. + facturapiOrgKeyEnc Bytes? @map("facturapi_org_key_enc") + facturapiOrgKeyIv Bytes? @map("facturapi_org_key_iv") + facturapiOrgKeyTag Bytes? @map("facturapi_org_key_tag") + + // Domicilio fiscal + codigoPostal String? @map("codigo_postal") @db.VarChar(5) + calle String? @db.VarChar(255) + numExterior String? @map("num_exterior") @db.VarChar(20) + numInterior String? @map("num_interior") @db.VarChar(20) + colonia String? @db.VarChar(255) + ciudad String? @db.VarChar(100) + municipio String? @db.VarChar(100) + estado String? @db.VarChar(100) + telefono String? @db.VarChar(20) + + // Preferencias de auto-facturación de pagos de suscripción. + // Default: facturar con datos del cliente cuando hay CSF disponible. + // Si `factPreferencia='publico_general'` siempre va a XAXX010101000. + factPreferencia String @default("mis_datos") @map("fact_preferencia") @db.VarChar(20) + // Uso CFDI default cuando se factura con datos del cliente. + // G03 = Gastos en general (más común para SaaS). + factUsoCfdi String @default("G03") @map("fact_uso_cfdi") @db.VarChar(5) + // Si el tenant tiene múltiples regímenes activos, cuál usar para factura. + // Null = usar el primero activo (heurística por createdAt). + factRegimenPreferido String? @map("fact_regimen_preferido") @db.VarChar(3) + + // === Despacho fields === + verticalProfile VerticalProfile? @map("vertical_profile") + dbMode DbMode? @map("db_mode") + dbConnectionEnc String? @map("db_connection_enc") + dbConnectionIv String? @map("db_connection_iv") + dbSchemaVersion Int @default(0) @map("db_schema_version") + connectorTokenEnc String? @map("connector_token_enc") + connectorTunnelHostname String? @map("connector_tunnel_hostname") + connectorLastSeen DateTime? @map("connector_last_seen") + connectorVersion String? @map("connector_version") @db.VarChar(20) + + memberships TenantMembership[] + fielCredential FielCredential? + satSyncJobs SatSyncJob[] + subscriptions Subscription[] + payments Payment[] + regimenesIgnorados TenantRegimenIgnorado[] + regimenesActivos TenantRegimenActivo[] + coeficientes CoeficienteUtilidad[] + timbreSuscripcion TimbreSuscripcion? + timbrePaquetes TimbrePaquete[] + connectorHeartbeats ConnectorHeartbeat[] + + @@map("tenants") +} + +model User { + id String @id @default(uuid()) + email String @unique + passwordHash String @map("password_hash") + nombre String + active Boolean @default(true) + lastLogin DateTime? @map("last_login") + createdAt DateTime @default(now()) @map("created_at") + // Contador para invalidar sesiones masivamente. Al incrementar, todos los + // JWT emitidos antes (con tokenVersion menor) quedan rechazados en el + // siguiente request. Se incrementa en: password change, password reset, + // logout-all. Default 0 para compat con users pre-rollout. + tokenVersion Int @default(0) @map("token_version") + // Último tenant que el user activó (via switch-tenant). Se usa para resolver + // el "tenant activo al login". Si es null, el login cae al primer membership + // por joinedAt. Se actualiza en cada switch. + lastTenantId String? @map("last_tenant_id") + // Cuenta sesiones (login exitoso, NO refresh). Usado para auto-dismiss del + // onboarding tras N logins. Default 0 → users pre-rollout siguen viendo el + // onboarding hasta acumular logins post-deploy. + loginCount Int @default(0) @map("login_count") + // Marca explícita de que el onboarding ya no debe mostrarse. Se setea cuando + // el user completa todos los pasos requeridos o desde el endpoint de dismiss. + onboardingDismissedAt DateTime? @map("onboarding_dismissed_at") + + memberships TenantMembership[] + platformRoles UserPlatformRole[] + passwordResetTokens PasswordResetToken[] + + @@map("users") +} + +/// Relación many-to-many entre User y Tenant. Permite que un mismo user (p.ej. +/// un dueño/contador) pertenezca a varios tenants con distintos roles. Esta +/// tabla es la fuente de verdad del "¿a qué tenants tiene acceso este user?". +/// +/// Durante la transición, `User.tenantId` y `User.rolId` se mantienen como +/// "default tenant" para login UX. El backfill inicial crea 1 membership por +/// user basado en esos campos. Cuando se agregue la UI de multi-tenant, los +/// nuevos accesos solo tocarán esta tabla. +model TenantMembership { + id Int @id @default(autoincrement()) + userId String @map("user_id") + tenantId String @map("tenant_id") + rolId Int @map("rol_id") + isOwner Boolean @default(false) @map("is_owner") + active Boolean @default(true) + joinedAt DateTime @default(now()) @map("joined_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + rol Rol @relation(fields: [rolId], references: [id]) + + @@unique([userId, tenantId]) + @@index([userId, active]) + @@index([tenantId, active]) + @@map("tenant_memberships") +} + +model Rol { + id Int @id @default(autoincrement()) + nombre String @unique @db.VarChar(20) + descripcion String? + createdAt DateTime @default(now()) @map("created_at") + + memberships TenantMembership[] + + @@map("roles") +} + +model RefreshToken { + id String @id @default(uuid()) + userId String @map("user_id") + token String @unique + expiresAt DateTime @map("expires_at") + createdAt DateTime @default(now()) @map("created_at") + + @@map("refresh_tokens") +} + +/// Tokens para recuperación de contraseña. Expiran en 1 hora, son single-use +/// (se marca `usedAt` al consumir). Al completar reset se invalidan todos los +/// refresh tokens del user — cierra todas sus sesiones forzando re-login. +model PasswordResetToken { + id String @id @default(uuid()) + userId String @map("user_id") + token String @unique + expiresAt DateTime @map("expires_at") + usedAt DateTime? @map("used_at") + createdAt DateTime @default(now()) @map("created_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) + @@index([expiresAt]) + @@map("password_reset_tokens") +} + +enum Plan { + trial + custom + business_control + business_cloud + mi_empresa + mi_empresa_plus +} + +enum VerticalProfile { + CONTABLE + JURIDICO + ARQUITECTURA +} + +enum DbMode { + BYO + MANAGED +} + + +// ============================================ +// Catálogo de Regímenes Fiscales SAT +// ============================================ + +model Regimen { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(3) + descripcion String + tipoPersona String @map("tipo_persona") @db.VarChar(20) // fisica, moral, ambos + activo Boolean @default(true) + createdAt DateTime @default(now()) @map("created_at") + + tenantIgnorados TenantRegimenIgnorado[] + tenantActivos TenantRegimenActivo[] + + @@map("regimenes") +} + +model TenantRegimenIgnorado { + id Int @id @default(autoincrement()) + tenantId String @map("tenant_id") + regimenId Int @map("regimen_id") + createdAt DateTime @default(now()) @map("created_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + regimen Regimen @relation(fields: [regimenId], references: [id], onDelete: Cascade) + + @@unique([tenantId, regimenId]) + @@map("tenant_regimenes_ignorados") +} + +model TenantRegimenActivo { + id Int @id @default(autoincrement()) + tenantId String @map("tenant_id") + regimenId Int @map("regimen_id") + createdAt DateTime @default(now()) @map("created_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + regimen Regimen @relation(fields: [regimenId], references: [id], onDelete: Cascade) + + @@unique([tenantId, regimenId]) + @@map("tenant_regimenes_activos") +} + +// ============================================ +// Catálogo de Eventos Fiscales +// ============================================ + +model EventoFiscalCatalogo { + id Int @id @default(autoincrement()) + titulo String + descripcion String? + tipo String @db.VarChar(20) // declaracion, pago, obligacion, informativa + diaBase Int @map("dia_base") // día del mes (17, 3, 31, etc.) + mesRelativo Int @default(1) @map("mes_relativo") // 1=mes posterior, 2=segundo mes posterior, 0=mes fijo + mesFijo Int? @map("mes_fijo") // para anuales: 2=feb, 3=mar, 4=abr + recurrencia String @default("mensual") @db.VarChar(20) // mensual, anual + usaExtensionRfc Boolean @default(false) @map("usa_extension_rfc") + regimenes String @default("todos") // 'todos' o CSV de claves: '601,603,612' + condicion String? @db.VarChar(50) // null, 'tiene_nomina', 'ingresos_4m' + activo Boolean @default(true) + createdAt DateTime @default(now()) @map("created_at") + + @@map("eventos_fiscales_catalogo") +} + +/// Lista negra SAT (Art. 69-B CFF) +model ListaNegra { + id Int @id @default(autoincrement()) + rfc String @unique @db.VarChar(13) + nombre String + situacion String @db.VarChar(30) // Definitivo, Presunto, Desvirtuado, Sentencia Favorable + updatedAt DateTime @updatedAt @map("updated_at") + createdAt DateTime @default(now()) @map("created_at") + + @@index([rfc]) + @@map("lista_negra") +} + +/// Días inhábiles fiscales (festivos oficiales de México) +model DiaInhabil { + id Int @id @default(autoincrement()) + fecha DateTime @unique @db.Date + nombre String + + @@map("dias_inhabiles") +} + +// ============================================ +// ISR Tables +// ============================================ + +/// Tasas RESICO (Art. 113-E) - tasa plana por bracket mensual +model IsrResicoTasa { + id Int @id @default(autoincrement()) + anio Int @map("anio") + montoMaximo Decimal @map("monto_maximo") @db.Decimal(18, 2) + porcentaje Decimal @db.Decimal(5, 2) + + @@unique([anio, montoMaximo]) + @@map("isr_resico_tasas") +} + +/// Tarifa ISR progresiva (Art. 96) - mensual +model IsrTarifa { + id Int @id @default(autoincrement()) + anio Int @map("anio") + limiteInferior Decimal @map("limite_inferior") @db.Decimal(18, 2) + limiteSuperior Decimal? @map("limite_superior") @db.Decimal(18, 2) + cuotaFija Decimal @map("cuota_fija") @db.Decimal(18, 2) + porcentajeExcedente Decimal @map("porcentaje_excedente") @db.Decimal(5, 2) + + @@unique([anio, limiteInferior]) + @@map("isr_tarifas") +} + +/// Coeficiente de utilidad por tenant/año (no se sobrescribe) +model CoeficienteUtilidad { + id Int @id @default(autoincrement()) + tenantId String @map("tenant_id") + anio Int @map("anio") + coeficiente Decimal @db.Decimal(10, 4) + createdAt DateTime @default(now()) @map("created_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + + @@unique([tenantId, anio]) + @@map("coeficiente_utilidad") +} + +// ============================================ +// SAT Sync Models +// ============================================ + +model FielCredential { + id String @id @default(uuid()) + tenantId String @unique @map("tenant_id") + rfc String @db.VarChar(13) + cerData Bytes @map("cer_data") + keyData Bytes @map("key_data") + keyPasswordEncrypted Bytes @map("key_password_encrypted") + cerIv Bytes @map("cer_iv") + cerTag Bytes @map("cer_tag") + keyIv Bytes @map("key_iv") + keyTag Bytes @map("key_tag") + passwordIv Bytes @map("password_iv") + passwordTag Bytes @map("password_tag") + serialNumber String? @map("serial_number") @db.VarChar(50) + validFrom DateTime @map("valid_from") + validUntil DateTime @map("valid_until") + isActive Boolean @default(true) @map("is_active") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + + @@map("fiel_credentials") +} + +model Subscription { + id String @id @default(uuid()) + tenantId String @map("tenant_id") + plan Plan + mpPreapprovalId String? @map("mp_preapproval_id") + status String @default("pending") + amount Decimal @db.Decimal(10, 2) + frequency String @default("monthly") + currentPeriodStart DateTime? @map("current_period_start") + currentPeriodEnd DateTime? @map("current_period_end") + // Cambio programado al próximo período (downgrades y cambios de frecuencia) + pendingPlan Plan? @map("pending_plan") + pendingFrequency String? @map("pending_frequency") + pendingEffectiveAt DateTime? @map("pending_effective_at") + // Upgrade inmediato en curso: preference MP esperando cobro prorateado. + // Cuando el webhook confirma el pago, se aplica el plan nuevo y se limpian estos campos. + upgradePreferenceId String? @map("upgrade_preference_id") + upgradeTargetPlan Plan? @map("upgrade_target_plan") + upgradeTargetAmount Decimal? @db.Decimal(10, 2) @map("upgrade_target_amount") + // Idempotencia del cron de aviso pre-vencimiento. Guarda el bucket de días + // que ya se notificó (7, 3, 1 ó 0) para no spamear al owner si el cron corre + // dos veces el mismo día. Se resetea cuando se renueva la suscripción o + // arranca un período nuevo. + lastReminderDay Int? @map("last_reminder_day") + lastReminderSentAt DateTime? @map("last_reminder_sent_at") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id]) + payments Payment[] + addons SubscriptionAddon[] + + @@index([tenantId]) + @@index([status]) + @@index([pendingEffectiveAt]) + @@map("subscriptions") +} + +model SubscriptionAddon { + id String @id @default(uuid()) + subscriptionId String @map("subscription_id") + planAddonCatalogoId String @map("plan_addon_catalogo_id") + /// UUID del contribuyente (entidad_id en tenant BD) cuando el add-on + /// aplica a un RFC específico. NULL para add-ons a nivel tenant (módulos + /// globales, +RFCs, +timbres). Sin FK porque contribuyente vive en BD tenant. + contribuyenteId String? @map("contribuyente_id") + mpPreapprovalId String? @map("mp_preapproval_id") + status String @default("pending") + quantity Int @default(1) + amount Decimal @db.Decimal(10, 2) + currentPeriodStart DateTime? @map("current_period_start") + currentPeriodEnd DateTime? @map("current_period_end") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + subscription Subscription @relation(fields: [subscriptionId], references: [id]) + planAddonCatalogo PlanAddonCatalogo @relation(fields: [planAddonCatalogoId], references: [id]) + + /// Sin UNIQUE compuesto: la validación de "un solo add-on activo por + /// (subscription, addon, contribuyente?)" queda a nivel aplicación + /// (findFirst en subscribeAddon), porque Postgres trata NULL!=NULL y no + /// hay forma trivial de enforcar unicidad con contribuyenteId opcional. + @@index([subscriptionId]) + @@index([subscriptionId, contribuyenteId]) + @@map("subscription_addons") +} + +/// Roles de plataforma (staff interno de Horux 360) — ortogonales al rol per-tenant. +/// Un user puede tener 0, 1 o varios roles. `platform_admin` es el superrol. +/// Ver `docs/plans/2026-04-14-platform-admin-roles.md`. +enum PlatformRole { + platform_admin // Todo: precios, clientes, facturas, suscripciones, gestión de staff + platform_ti // Mismos permisos que admin (equipo de TI / tech ops). Diferencia solo en trazabilidad. + platform_support // Ver todos los tenants, resolver tickets, NO facturación/precios + platform_sales // Crear/editar tenants (onboarding), ver suscripciones, NO precios + platform_finance // Ver payments, emitir facturas manuales, editar precios, reportes fiscales +} + +model UserPlatformRole { + id Int @id @default(autoincrement()) + userId String @map("user_id") + role PlatformRole + createdAt DateTime @default(now()) @map("created_at") + createdBy String? @map("created_by") // User.id de quien asignó (audit trail) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([userId, role]) + @@index([role]) + @@map("user_platform_roles") +} + +/// Registro de acciones críticas para auditoría (SAT compliance, forense, disputas). +/// Se instrumenta vía `utils/audit.ts` con helper fire-and-forget — un fallo al +/// escribir aquí NUNCA debe romper la acción principal. +model AuditLog { + id String @id @default(uuid()) + userId String? @map("user_id") + tenantId String? @map("tenant_id") + action String @db.VarChar(64) // "price.updated", "subscription.cancelled", etc. + entityType String? @map("entity_type") @db.VarChar(32) + entityId String? @map("entity_id") + metadata Json? // before/after, ip, userAgent, contexto + createdAt DateTime @default(now()) @map("created_at") + + @@index([userId, createdAt]) + @@index([tenantId, createdAt]) + @@index([action, createdAt]) + @@index([entityType, entityId]) + @@map("audit_log") +} + +/// Padrón persistente de RFCs que ya consumieron su prueba gratuita de 30 días. +/// Sobrevive al ciclo de vida del Tenant (si se borra/recrea, el RFC sigue aquí), +/// bloqueando el abuso de "registro nuevo con el mismo RFC para otro trial". +model TrialUsage { + id Int @id @default(autoincrement()) + rfc String @unique @db.VarChar(13) + tenantId String? @map("tenant_id") // Tenant que consumió (null si el tenant se borró después) + startedAt DateTime @default(now()) @map("started_at") + + @@map("trial_usages") +} + +/// Catálogo despacho — precios + limits editables por admin global. +/// Las `features` siguen viviendo en TS (`DESPACHO_PLANS` en `@horux/shared`) +/// porque están acopladas a UI/middleware y son contrato de código. +/// Incluye filas para `trial` y `custom` (sin precios — null). +model DespachoPlanPrice { + plan String @id // trial | custom | mi_empresa | mi_empresa_plus | business_control | business_cloud + nombre String @db.VarChar(50) + monthly Decimal? @db.Decimal(10, 2) + firstYear Decimal? @db.Decimal(10, 2) @map("first_year") + renewal Decimal? @db.Decimal(10, 2) + permiteMonthly Boolean @default(false) @map("permite_monthly") + /// Limits del plan. -1 = ilimitado donde aplique (maxUsers). + maxRfcs Int @map("max_rfcs") + maxUsers Int @map("max_users") + timbresIncluidosMes Int @default(0) @map("timbres_incluidos_mes") + dbMode DbMode @map("db_mode") + permiteServidorBackup Boolean @default(false) @map("permite_servidor_backup") + /// Habilita SAT incremental (3 syncs/día adicionales al daily). Mi Empresa +, + /// Business Control y Enterprise lo tienen activo por default; planes + /// inferiores se quedan solo con el daily de las 03:00. + permiteSatIncremental Boolean @default(false) @map("permite_sat_incremental") + updatedAt DateTime @updatedAt @map("updated_at") + + @@map("despacho_plan_prices") +} + +model PlanAddonCatalogo { + id String @id @default(uuid()) + codename String @unique @db.VarChar(50) + nombre String + verticalProfile VerticalProfile? + precio Decimal @db.Decimal(10, 2) + frecuencia String @db.VarChar(10) + delta Json + active Boolean @default(true) + createdAt DateTime @default(now()) @map("created_at") + + subscriptionAddons SubscriptionAddon[] + + @@map("plan_addon_catalogo") +} + +model ConnectorHeartbeat { + id String @id @default(uuid()) + tenantId String @map("tenant_id") + timestamp DateTime @default(now()) + latencyMs Int @map("latency_ms") + version String @db.VarChar(20) + pgVersion String? @map("pg_version") @db.VarChar(50) + status String @db.VarChar(20) + errorMsg String? @map("error_msg") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + + @@index([tenantId, timestamp]) + @@map("connector_heartbeats") +} + +enum PaymentKind { + subscription + timbres_pack +} + +model Payment { + id String @id @default(uuid()) + tenantId String @map("tenant_id") + subscriptionId String? @map("subscription_id") + mpPaymentId String? @map("mp_payment_id") + amount Decimal @db.Decimal(10, 2) + status String @default("pending") + paymentMethod String? @map("payment_method") + paidAt DateTime? @map("paid_at") + // Tipo de pago. subscription = cobro mensual/anual del plan. + // timbres_pack = compra de paquete de timbres adicionales. + kind PaymentKind @default(subscription) + // ID de la factura emitida auto por Facturapi. Null si no se facturó: + // primer pago (manual), trial sin monto, o fallo al emitir. + facturapiInvoiceId String? @map("facturapi_invoice_id") + createdAt DateTime @default(now()) @map("created_at") + + tenant Tenant @relation(fields: [tenantId], references: [id]) + subscription Subscription? @relation(fields: [subscriptionId], references: [id]) + timbrePaquete TimbrePaquete? + + @@index([tenantId]) + @@index([subscriptionId]) + @@map("payments") +} + +/// Catálogo de paquetes de timbres adicionales vendibles. Precios editables +/// desde panel admin. Los 3 defaults (100/$200, 1000/$1400, 10000/$8600) se +/// insertan en seed idempotente. +model TimbrePaqueteCatalogo { + id Int @id @default(autoincrement()) + cantidad Int @unique // 100, 1000, 10000 + precio Decimal @db.Decimal(10, 2) + active Boolean @default(true) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@map("timbre_paquetes_catalogo") +} + +/// Compra individual de timbres adicionales. Los timbres del plan (mensuales) +/// se rastrean en TimbreSuscripcion — esto es SOLO para los extras pagados. +/// Vigencia 1 año desde `adquiridoEn`. El orden de consumo es FIFO por +/// `expiraEn` (menor primero) para no desperdiciar paquetes próximos a vencer. +model TimbrePaquete { + id Int @id @default(autoincrement()) + tenantId String @map("tenant_id") + paymentId String? @unique @map("payment_id") // Payment que lo compró; null si admin grant manual + cantidad Int // cuántos timbres tenía originalmente + usados Int @default(0) + precio Decimal @db.Decimal(10, 2) // precio pagado (historial, no cambia si el catálogo cambia) + adquiridoEn DateTime @default(now()) @map("adquirido_en") + expiraEn DateTime @map("expira_en") // adquiridoEn + 1 año + createdAt DateTime @default(now()) @map("created_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + payment Payment? @relation(fields: [paymentId], references: [id]) + + @@index([tenantId, expiraEn]) + @@map("timbre_paquetes") +} + +model SatSyncJob { + id String @id @default(uuid()) + tenantId String @map("tenant_id") + contribuyenteId String? @map("contribuyente_id") + type SatSyncType + status SatSyncStatus @default(pending) + dateFrom DateTime @map("date_from") @db.Date + dateTo DateTime @map("date_to") @db.Date + cfdiType CfdiSyncType? @map("cfdi_type") + satRequestId String? @map("sat_request_id") @db.VarChar(50) + // Mapa { kindKey: requestId } de TODOS los requests creados durante el job. + // Permite que retries reusen requestIds previos en lugar de quemar cuota + // del SAT creando nuevos. kindKey = `${requestType}-${tipoCfdi}-${from}-${to}`. + satRequestIds Json @default("{}") @map("sat_request_ids") + satPackageIds String[] @map("sat_package_ids") + cfdisFound Int @default(0) @map("cfdis_found") + cfdisDownloaded Int @default(0) @map("cfdis_downloaded") + cfdisInserted Int @default(0) @map("cfdis_inserted") + cfdisUpdated Int @default(0) @map("cfdis_updated") + progressPercent Int @default(0) @map("progress_percent") + errorMessage String? @map("error_message") + startedAt DateTime? @map("started_at") + completedAt DateTime? @map("completed_at") + createdAt DateTime @default(now()) @map("created_at") + retryCount Int @default(0) @map("retry_count") + nextRetryAt DateTime? @map("next_retry_at") + // True cuando el job es `initial` con rango de fechas personalizado por el + // usuario (botón UI). Cambia la política de retry: 2 intentos vs 3 del + // bootstrap puro. Daily/incremental ignoran este campo. + isCustomRange Boolean @default(false) @map("is_custom_range") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + + @@index([tenantId]) + @@index([status]) + @@index([status, nextRetryAt]) + @@map("sat_sync_jobs") +} + +enum SatSyncType { + initial + daily + incremental +} + +enum SatSyncStatus { + pending + running + completed + failed +} + +enum CfdiSyncType { + emitidos + recibidos +} + +// ============================================ +// Catálogos SAT para Facturación (CFDI 4.0) +// ============================================ + +model CatFormaPago { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(2) + descripcion String + + @@map("cat_forma_pago") +} + +model CatMetodoPago { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(3) + descripcion String + + @@map("cat_metodo_pago") +} + +model CatUsoCfdi { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(4) + descripcion String + personaFisica Boolean @default(true) @map("persona_fisica") + personaMoral Boolean @default(true) @map("persona_moral") + + @@map("cat_uso_cfdi") +} + +model CatMoneda { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(3) + descripcion String + decimales Int @default(2) + + @@map("cat_moneda") +} + +model CatClaveUnidad { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(10) + descripcion String + + @@map("cat_clave_unidad") +} + +model CatClaveProdServ { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(8) + descripcion String + + @@index([descripcion]) + @@map("cat_clave_prod_serv") +} + +model CatObjetoImp { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(2) + descripcion String + + @@map("cat_objeto_imp") +} + +model CatTipoRelacion { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(2) + descripcion String + + @@map("cat_tipo_relacion") +} + +model CatExportacion { + id Int @id @default(autoincrement()) + clave String @unique @db.VarChar(2) + descripcion String + + @@map("cat_exportacion") +} + +// ============================================ +// Gestión de Timbres Facturapi +// ============================================ + +model TimbreSuscripcion { + id Int @id @default(autoincrement()) + tenantId String @unique @map("tenant_id") + tipo String @db.VarChar(10) // mensual, anual + timbresLimite Int @map("timbres_limite") // 50 o 600 + timbresUsados Int @default(0) @map("timbres_usados") + periodoInicio DateTime @map("periodo_inicio") @db.Date + periodoFin DateTime @map("periodo_fin") @db.Date + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + + @@map("timbre_suscripciones") +} diff --git a/apps/api/prisma/seed.ts b/apps/api/prisma/seed.ts new file mode 100644 index 0000000..77c0b03 --- /dev/null +++ b/apps/api/prisma/seed.ts @@ -0,0 +1,528 @@ +import { PrismaClient } from '@prisma/client'; +import bcrypt from 'bcryptjs'; +import { Pool } from 'pg'; +import { migrate } from '../src/config/tenant-migrations.js'; +import { RESICO_TASAS, ISR_TARIFAS } from './isr-data.js'; +import { EVENTOS_FISCALES, DIAS_INHABILES } from './eventos-fiscales-data.js'; +import { + FORMAS_PAGO, METODOS_PAGO, USOS_CFDI, MONEDAS, CLAVES_UNIDAD, + OBJETOS_IMP, TIPOS_RELACION, EXPORTACIONES, +} from './catalogos-sat-data.js'; + +const prisma = new PrismaClient(); + +function parseDatabaseUrl(url: string) { + const parsed = new URL(url); + return { + host: parsed.hostname, + port: parseInt(parsed.port || '5432'), + user: decodeURIComponent(parsed.username), + password: decodeURIComponent(parsed.password), + }; +} + +const REGIMENES_SAT = [ + { clave: '601', descripcion: 'General de Ley Personas Morales', tipoPersona: 'moral' }, + { clave: '603', descripcion: 'Personas Morales con Fines no Lucrativos', tipoPersona: 'moral' }, + { clave: '605', descripcion: 'Sueldos y Salarios e Ingresos Asimilados a Salarios', tipoPersona: 'fisica' }, + { clave: '606', descripcion: 'Arrendamiento', tipoPersona: 'fisica' }, + { clave: '607', descripcion: 'Régimen de Enajenación o Adquisición de Bienes', tipoPersona: 'fisica' }, + { clave: '608', descripcion: 'Demás ingresos', tipoPersona: 'fisica' }, + { clave: '610', descripcion: 'Residentes en el Extranjero sin Establecimiento Permanente en México', tipoPersona: 'ambos' }, + { clave: '611', descripcion: 'Ingresos por Dividendos (socios y accionistas)', tipoPersona: 'fisica' }, + { clave: '612', descripcion: 'Personas Físicas con Actividades Empresariales y Profesionales', tipoPersona: 'fisica' }, + { clave: '614', descripcion: 'Ingresos por intereses', tipoPersona: 'fisica' }, + { clave: '615', descripcion: 'Régimen de los ingresos por obtención de premios', tipoPersona: 'fisica' }, + { clave: '616', descripcion: 'Sin obligaciones fiscales', tipoPersona: 'ambos' }, + { clave: '620', descripcion: 'Sociedades Cooperativas de Producción que optan por diferir sus ingresos', tipoPersona: 'moral' }, + { clave: '621', descripcion: 'Incorporación Fiscal', tipoPersona: 'fisica' }, + { clave: '622', descripcion: 'Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras', tipoPersona: 'ambos' }, + { clave: '623', descripcion: 'Opcional para Grupos de Sociedades', tipoPersona: 'moral' }, + { clave: '624', descripcion: 'Coordinados', tipoPersona: 'moral' }, + { clave: '625', descripcion: 'Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas', tipoPersona: 'fisica' }, + { clave: '626', descripcion: 'Régimen Simplificado de Confianza', tipoPersona: 'ambos' }, +]; + +async function main() { + console.log('🌱 Seeding database...'); + + // Seed regimenes catalog + for (const r of REGIMENES_SAT) { + await prisma.regimen.upsert({ + where: { clave: r.clave }, + update: { descripcion: r.descripcion, tipoPersona: r.tipoPersona }, + create: r, + }); + } + console.log(`✅ ${REGIMENES_SAT.length} regímenes fiscales SAT cargados`); + + // Seed ISR tables — limpiar y recrear + await prisma.isrResicoTasa.deleteMany(); + await prisma.isrTarifa.deleteMany(); + + for (const anio of [2020, 2021, 2022, 2023, 2024, 2025, 2026]) { + if (anio >= 2022) { + await prisma.isrResicoTasa.createMany({ + data: RESICO_TASAS.map(t => ({ anio, montoMaximo: t.montoMaximo, porcentaje: t.porcentaje })), + }); + } + + const tarifas = ISR_TARIFAS[anio]; + if (tarifas) { + await prisma.isrTarifa.createMany({ + data: tarifas.map(t => ({ + anio, + limiteInferior: t.li, + limiteSuperior: t.ls, + cuotaFija: t.cf, + porcentajeExcedente: t.pe, + })), + }); + } + } + console.log('✅ Tablas ISR 2020-2026 cargadas'); + + // Seed eventos fiscales catálogo + await prisma.eventoFiscalCatalogo.deleteMany(); + await prisma.eventoFiscalCatalogo.createMany({ + data: EVENTOS_FISCALES.map(e => ({ + titulo: e.titulo, + tipo: e.tipo, + diaBase: e.diaBase, + mesRelativo: e.mesRelativo, + mesFijo: (e as any).mesFijo || null, + recurrencia: e.recurrencia, + usaExtensionRfc: e.usaExtensionRfc, + regimenes: e.regimenes, + condicion: e.condicion || null, + })), + }); + console.log(`✅ ${EVENTOS_FISCALES.length} eventos fiscales cargados`); + + // Seed días inhábiles + await prisma.diaInhabil.deleteMany(); + await prisma.diaInhabil.createMany({ + data: DIAS_INHABILES.map(d => ({ fecha: new Date(d.fecha), nombre: d.nombre })), + skipDuplicates: true, + }); + console.log(`✅ ${DIAS_INHABILES.length} días inhábiles cargados (2020-2027)`); + + // Seed catálogos SAT para facturación + for (const fp of FORMAS_PAGO) { + await prisma.catFormaPago.upsert({ where: { clave: fp.clave }, update: { descripcion: fp.descripcion }, create: fp }); + } + console.log(`✅ ${FORMAS_PAGO.length} formas de pago cargadas`); + + for (const mp of METODOS_PAGO) { + await prisma.catMetodoPago.upsert({ where: { clave: mp.clave }, update: { descripcion: mp.descripcion }, create: mp }); + } + console.log(`✅ ${METODOS_PAGO.length} métodos de pago cargados`); + + for (const u of USOS_CFDI) { + await prisma.catUsoCfdi.upsert({ where: { clave: u.clave }, update: { descripcion: u.descripcion, personaFisica: u.personaFisica, personaMoral: u.personaMoral }, create: u }); + } + console.log(`✅ ${USOS_CFDI.length} usos CFDI cargados`); + + for (const m of MONEDAS) { + await prisma.catMoneda.upsert({ where: { clave: m.clave }, update: { descripcion: m.descripcion, decimales: m.decimales }, create: m }); + } + console.log(`✅ ${MONEDAS.length} monedas cargadas`); + + for (const cu of CLAVES_UNIDAD) { + await prisma.catClaveUnidad.upsert({ where: { clave: cu.clave }, update: { descripcion: cu.descripcion }, create: cu }); + } + console.log(`✅ ${CLAVES_UNIDAD.length} claves de unidad cargadas`); + + for (const oi of OBJETOS_IMP) { + await prisma.catObjetoImp.upsert({ where: { clave: oi.clave }, update: { descripcion: oi.descripcion }, create: oi }); + } + console.log(`✅ ${OBJETOS_IMP.length} objetos de impuesto cargados`); + + for (const tr of TIPOS_RELACION) { + await prisma.catTipoRelacion.upsert({ where: { clave: tr.clave }, update: { descripcion: tr.descripcion }, create: tr }); + } + console.log(`✅ ${TIPOS_RELACION.length} tipos de relación cargados`); + + for (const ex of EXPORTACIONES) { + await prisma.catExportacion.upsert({ where: { clave: ex.clave }, update: { descripcion: ex.descripcion }, create: ex }); + } + console.log(`✅ ${EXPORTACIONES.length} exportaciones cargadas`); + + // Tabla `plan_prices` (modelo PlanPrice) era el catálogo Horux 360 legacy. + // Tras eliminar los planes legacy (2026-04-30), no se siembran filas. Los + // precios despacho viven en `despacho_plan_prices` (modelo DespachoPlanPrice). + + // Catálogo despacho — precios + limits. UPSERT idempotente: precios y limits + // se actualizan al re-correr seed (decisión: el seed es source of truth para + // valores iniciales; el admin puede sobreescribir vía UI y NO debe re-correr + // seed si no quiere perder ajustes manuales). Si quieres preservar edits del + // admin, cambiar `update` a `{}` y aplicar manualmente. + const DESPACHO_PLAN_CATALOGO = [ + { plan: 'trial', nombre: 'Prueba', monthly: null, firstYear: null, renewal: null, permiteMonthly: false, maxRfcs: 3, maxUsers: 1, timbresIncluidosMes: 0, dbMode: 'MANAGED' as const, permiteServidorBackup: false, permiteSatIncremental: false }, + { plan: 'custom', nombre: 'Custom', monthly: null, firstYear: null, renewal: null, permiteMonthly: false, maxRfcs: 1, maxUsers: 3, timbresIncluidosMes: 50, dbMode: 'MANAGED' as const, permiteServidorBackup: false, permiteSatIncremental: false }, + { plan: 'mi_empresa', nombre: 'Mi Empresa', monthly: 580, firstYear: 5800, renewal: 5800, permiteMonthly: true, maxRfcs: 1, maxUsers: 3, timbresIncluidosMes: 50, dbMode: 'MANAGED' as const, permiteServidorBackup: false, permiteSatIncremental: false }, + { plan: 'mi_empresa_plus', nombre: 'Mi Empresa +', monthly: 900, firstYear: 9000, renewal: 9000, permiteMonthly: true, maxRfcs: 1, maxUsers: 3, timbresIncluidosMes: 50, dbMode: 'MANAGED' as const, permiteServidorBackup: false, permiteSatIncremental: true }, + { plan: 'business_control', nombre: 'Business Control', monthly: null, firstYear: 25850, renewal: 25850, permiteMonthly: false, maxRfcs: 100, maxUsers: -1, timbresIncluidosMes: 0, dbMode: 'BYO' as const, permiteServidorBackup: true, permiteSatIncremental: true }, + { plan: 'business_cloud', nombre: 'Enterprise', monthly: null, firstYear: 43000, renewal: 43000, permiteMonthly: false, maxRfcs: 100, maxUsers: -1, timbresIncluidosMes: 0, dbMode: 'BYO' as const, permiteServidorBackup: true, permiteSatIncremental: true }, + ]; + for (const p of DESPACHO_PLAN_CATALOGO) { + await prisma.despachoPlanPrice.upsert({ + where: { plan: p.plan }, + update: { ...p }, + create: { ...p }, + }); + } + console.log(`✅ ${DESPACHO_PLAN_CATALOGO.length} planes despacho cargados (precios + limits)`); + + // Catálogo de paquetes de timbres adicionales. Editables desde panel admin. + // Se crean con upsert por `cantidad` (unique) — permite reejecutar seed sin + // sobrescribir precios ya ajustados manualmente: si el row existe, update + // NO toca el precio (solo active + updatedAt si hace falta), sólo lo crea + // si no existía. Si se quiere forzar reset de precios, borrar las filas. + const TIMBRE_PAQUETES = [ + { cantidad: 100, precio: 200 }, + { cantidad: 1000, precio: 1400 }, + { cantidad: 10000, precio: 8600 }, + ]; + for (const p of TIMBRE_PAQUETES) { + await prisma.timbrePaqueteCatalogo.upsert({ + where: { cantidad: p.cantidad }, + update: {}, // No tocamos `precio` si ya existe (admin pudo editarlo) + create: { cantidad: p.cantidad, precio: p.precio, active: true }, + }); + } + console.log(`✅ ${TIMBRE_PAQUETES.length} paquetes de timbres en catálogo`); + + const databaseName = 'horux_ede123456ab1'; + + // Create demo tenant + const tenant = await prisma.tenant.upsert({ + where: { rfc: 'EDE123456AB1' }, + update: {}, + create: { + nombre: 'Empresa Demo SA de CV', + rfc: 'EDE123456AB1', + plan: 'mi_empresa_plus', + databaseName, + }, + }); + + console.log('✅ Tenant created:', tenant.nombre); + + // Migración: renombra el rol legacy 'admin' a 'owner' si sobrevive de un seed viejo. + // Idempotente (no-op si ya se renombró o nunca existió). + await prisma.$executeRawUnsafe(`UPDATE roles SET nombre = 'owner' WHERE nombre = 'admin'`); + + // Backfill de trial_usages para tenants que ya consumieron su trial antes de que + // existiera esta tabla. Idempotente: ON CONFLICT DO NOTHING. Filtramos por + // longitud porque trial_usages.rfc es varchar(13) y los tenants despacho usan + // slugs largos (DESPACHO_xxx) que no encajan — el padrón anti-abuso de trial + // solo aplica a RFCs SAT reales de personas/empresas, no a slugs. + await prisma.$executeRawUnsafe(` + INSERT INTO trial_usages (rfc, tenant_id, started_at) + SELECT UPPER(rfc), id, COALESCE(created_at, NOW()) + FROM tenants + WHERE trial_ends_at IS NOT NULL AND LENGTH(rfc) <= 13 + ON CONFLICT (rfc) DO NOTHING + `); + + // Backfill de user_platform_roles: los owners del tenant HTS240708LJA se + // convierten automáticamente en platform_admin. Migrado a tenant_memberships + // tras F6 (User.tenantId/rolId eliminados). Idempotente. + await prisma.$executeRawUnsafe(` + INSERT INTO user_platform_roles (user_id, role, created_at) + SELECT tm.user_id, 'platform_admin'::"PlatformRole", NOW() + FROM tenant_memberships tm + JOIN tenants t ON tm.tenant_id = t.id + WHERE t.rfc = 'HTS240708LJA' AND tm.is_owner = true AND tm.active = true + ON CONFLICT (user_id, role) DO NOTHING + `); + + // (Backfill de tenant_memberships eliminado — F6 ya migró todos los users + // legacy y los campos `User.tenantId` y `User.rolId` ya no existen. Los + // users nuevos se crean directamente con su membership.) + + // Seed roles + const rolesData = [ + { nombre: 'owner', descripcion: 'Dueño - acceso completo' }, + { nombre: 'cfo', descripcion: 'CFO - acceso completo (mismo nivel que el dueño)' }, + { nombre: 'contador', descripcion: 'Contador - dashboard, CFDI, impuestos, calendario, alertas, facturación' }, + { nombre: 'auxiliar', descripcion: 'Auxiliar - mismos permisos que contador' }, + { nombre: 'visor', descripcion: 'Visor - solo lectura de CFDI, impuestos, calendario, alertas' }, + ]; + for (const r of rolesData) { + await prisma.rol.upsert({ + where: { nombre: r.nombre }, + update: { descripcion: r.descripcion }, + create: r, + }); + } + + // Seed despacho roles + await prisma.rol.upsert({ + where: { nombre: 'supervisor' }, + update: {}, + create: { id: 9, nombre: 'supervisor', descripcion: 'Supervisor de despacho — titular de RFCs, crea carteras' }, + }); + + await prisma.rol.upsert({ + where: { nombre: 'cliente' }, + update: {}, + create: { id: 10, nombre: 'cliente', descripcion: 'Cliente visor externo — acceso read-only a sus RFCs' }, + }); + + const roles = await prisma.rol.findMany(); + const rolMap = new Map(roles.map(r => [r.nombre, r.id])); + console.log(`✅ ${roles.length} roles cargados`); + + // Create demo users + const passwordHash = await bcrypt.hash('demo123', 12); + + const users = [ + { email: 'admin@demo.com', nombre: 'Dueño Demo', rolNombre: 'owner' }, + { email: 'contador@demo.com', nombre: 'Contador Demo', rolNombre: 'contador' }, + { email: 'visor@demo.com', nombre: 'Visor Demo', rolNombre: 'visor' }, + ]; + + for (const userData of users) { + const rolId = rolMap.get(userData.rolNombre)!; + const user = await prisma.user.upsert({ + where: { email: userData.email }, + update: {}, + create: { + email: userData.email, + passwordHash, + nombre: userData.nombre, + lastTenantId: tenant.id, + }, + }); + // Membership al tenant demo (idempotente — F6 multi-tenant: la autorización + // vive en tenant_memberships, no en User.tenantId/rolId). + await prisma.tenantMembership.upsert({ + where: { userId_tenantId: { userId: user.id, tenantId: tenant.id } }, + update: { rolId, isOwner: userData.rolNombre === 'owner' || userData.rolNombre === 'cfo', active: true }, + create: { + userId: user.id, + tenantId: tenant.id, + rolId, + isOwner: userData.rolNombre === 'owner' || userData.rolNombre === 'cfo', + active: true, + }, + }); + console.log(`✅ User created: ${user.email} (${userData.rolNombre})`); + } + + // Create tenant database + const dbConfig = parseDatabaseUrl(process.env.DATABASE_URL!); + const adminPool = new Pool({ ...dbConfig, database: 'postgres', max: 1 }); + + try { + const exists = await adminPool.query( + `SELECT 1 FROM pg_database WHERE datname = $1`, + [databaseName] + ); + + if (exists.rows.length === 0) { + await adminPool.query(`CREATE DATABASE "${databaseName}"`); + console.log(`✅ Tenant database created: ${databaseName}`); + } else { + console.log(`ℹ️ Tenant database already exists: ${databaseName}`); + } + } finally { + await adminPool.end(); + } + + // Create tables in tenant database + const tenantPool = new Pool({ ...dbConfig, database: databaseName, max: 1 }); + + try { + // Reset tenant tables so the re-seed parte de cero. Luego corremos las + // migraciones (fuente única de verdad del schema tenant) para garantizar + // que queden todas las tablas y columnas actuales. + await tenantPool.query(` + DROP TABLE IF EXISTS cfdi_conceptos CASCADE; + DROP TABLE IF EXISTS cfdis CASCADE; + DROP TABLE IF EXISTS conciliaciones CASCADE; + DROP TABLE IF EXISTS bancos CASCADE; + DROP TABLE IF EXISTS recordatorios CASCADE; + DROP TABLE IF EXISTS alertas CASCADE; + DROP TABLE IF EXISTS rfcs CASCADE; + DROP TABLE IF EXISTS opiniones_cumplimiento CASCADE; + DROP TABLE IF EXISTS schema_migrations; + `); + + await migrate(tenantPool, tenant.rfc); + console.log('✅ Tenant schema aplicado vía migraciones'); + + // Bloque legacy de CREATE TABLE / CREATE INDEX retirado: vive ahora en + // `apps/api/src/migrations/tenant/*.sql` (fuente única de verdad). + + // Insert demo CFDIs with new structure + const cfdiTypes = ['EMITIDO', 'RECIBIDO']; + const tipoComprobantes: Record = { EMITIDO: 'I', RECIBIDO: 'I' }; + const rfcs = ['XAXX010101000', 'MEXX020202000', 'AAXX030303000', 'BBXX040404000']; + const nombres = ['Cliente Demo SA', 'Proveedor ABC', 'Servicios XYZ', 'Materiales 123']; + + for (let i = 0; i < 50; i++) { + const tipo = cfdiTypes[i % 2]; + const rfcIndex = i % 4; + const subtotal = Math.floor(Math.random() * 50000) + 1000; + const iva = subtotal * 0.16; + const total = subtotal + iva; + const daysAgo = Math.floor(Math.random() * 180); + const fecha = new Date(); + fecha.setDate(fecha.getDate() - daysAgo); + const year = String(fecha.getFullYear()); + const month = String(fecha.getMonth() + 1).padStart(2, '0'); + + // Sin ON CONFLICT: las tablas se dropean en línea 342-352 antes de seed + // y los UUIDs son crypto.randomUUID() (probabilidad de colisión ~0). + // El UNIQUE en cfdis es funcional (LOWER(uuid)), no acepta ON CONFLICT + // por columna plana — ver migración 027_cfdi_uuid_unique_case_insensitive. + await tenantPool.query(` + INSERT INTO cfdis ( + year, month, type, uuid, serie, folio, status, fecha_emision, + rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor, + subtotal, subtotal_mxn, descuento, descuento_mxn, + total, total_mxn, moneda, tipo_cambio, tipo_comprobante, + metodo_pago, iva_traslado, iva_traslado_mxn, + regimen_fiscal_emisor, regimen_fiscal_receptor + ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26) + `, [ + year, month, tipo, crypto.randomUUID(), 'A', String(1000 + i), + 'Vigente', fecha, + tipo === 'EMITIDO' ? 'EDE123456AB1' : rfcs[rfcIndex], + tipo === 'EMITIDO' ? 'Empresa Demo SA de CV' : nombres[rfcIndex], + tipo === 'RECIBIDO' ? 'EDE123456AB1' : rfcs[rfcIndex], + tipo === 'RECIBIDO' ? 'Empresa Demo SA de CV' : nombres[rfcIndex], + subtotal, subtotal, 0, 0, + total, total, 'MXN', 1, tipoComprobantes[tipo], + 'PUE', iva, iva, + '601', '601', + ]); + } + + console.log('✅ Demo CFDIs created (50)'); + + // Insert demo conceptos for each CFDI + const { rows: allCfdis } = await tenantPool.query(`SELECT id FROM cfdis`); + const productos = ['Servicio de consultoría', 'Licencia de software', 'Soporte técnico', 'Desarrollo web', 'Capacitación']; + + for (const c of allCfdis) { + const numConceptos = Math.floor(Math.random() * 3) + 1; + for (let j = 0; j < numConceptos; j++) { + const cantidad = Math.floor(Math.random() * 5) + 1; + const valorUnitario = Math.floor(Math.random() * 5000) + 500; + const importe = cantidad * valorUnitario; + const iva = importe * 0.16; + + await tenantPool.query(` + INSERT INTO cfdi_conceptos ( + cfdi_id, clave_prod_serv, descripcion, cantidad, clave_unidad, unidad, + valor_unitario, valor_unitario_mxn, + importe, importe_mxn, descuento, descuento_mxn, + iva_traslado, iva_traslado_mxn + ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) + `, [ + c.id, + '84111506', productos[j % productos.length], cantidad, 'E48', 'Servicio', + valorUnitario, valorUnitario, + importe, importe, 0, 0, + iva, iva, + ]); + } + } + + console.log('✅ Demo conceptos created'); + + } finally { + await tenantPool.end(); + } + + // (PlanCatalogo seed eliminado — el modelo se dropeó en migración + // 20260430200000_drop_plan_catalogo_orphan; el catálogo despacho vive en + // `despacho_plan_prices` y se siembra arriba en DESPACHO_PLAN_CATALOGO.) + + // Seed addon catalog + const addonCatalogoData = [ + { + codename: 'rfcs_extra_10', + nombre: '+10 RFCs adicionales', + verticalProfile: 'CONTABLE' as const, + precio: 190, + frecuencia: 'mensual', + delta: { maxRfcs: 10 }, + }, + { + codename: 'rfcs_extra_50', + nombre: '+50 RFCs adicionales', + verticalProfile: 'CONTABLE' as const, + precio: 690, + frecuencia: 'mensual', + delta: { maxRfcs: 50 }, + }, + { + codename: 'timbres_extra_500', + nombre: '+500 timbres mensuales', + precio: 490, + frecuencia: 'mensual', + delta: { timbresIncluidosMes: 500 }, + }, + { + codename: 'modulo_ia', + nombre: 'Módulo IA Fiscal', + precio: 390, + frecuencia: 'mensual', + delta: { features: ['ia_lolita'] }, + }, + { + // Lolita IA activable por contribuyente específico del despacho. + // SubscriptionAddon.contribuyenteId apunta al RFC que lo contrata. + // Cobro mensual en preapproval propio (la licencia del despacho es anual; + // el add-on va en ciclo independiente). + codename: 'lolita_ia_contribuyente', + nombre: 'Lolita IA (por contribuyente)', + verticalProfile: 'CONTABLE' as const, + precio: 250, + frecuencia: 'mensual', + delta: { features: ['ia_lolita'] }, + }, + { + // Contribuyente adicional para planes Business Control y Enterprise + // (ambos incluyen 100 base). Se cobra automáticamente según overage; no + // requiere opt-in, pero se modela como add-on para que el preapproval MP + // lo cubra. El codename mantiene el sufijo "business_cloud" por compat + // con suscripciones existentes; el nombre display ya es genérico. + codename: 'contribuyente_extra_business_cloud', + nombre: 'Contribuyente adicional (RFC extra)', + verticalProfile: 'CONTABLE' as const, + precio: 45, + frecuencia: 'mensual', + delta: { maxRfcs: 1 }, + }, + ]; + + for (const a of addonCatalogoData) { + await prisma.planAddonCatalogo.upsert({ + where: { codename: a.codename }, + update: { nombre: a.nombre, precio: a.precio, delta: a.delta }, + create: { ...a, verticalProfile: a.verticalProfile ?? null }, + }); + } + console.log('✓ Addon catalog seeded (6 addons)'); + + console.log('\n🎉 Seed completed successfully!'); + console.log('\n📝 Demo credentials:'); + console.log(' Admin: admin@demo.com / demo123'); + console.log(' Contador: contador@demo.com / demo123'); + console.log(' Visor: visor@demo.com / demo123'); +} + +main() + .catch((e) => { + console.error('Error seeding database:', e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/apps/api/scripts/apply-migration-042.ts b/apps/api/scripts/apply-migration-042.ts new file mode 100644 index 0000000..5cc6534 --- /dev/null +++ b/apps/api/scripts/apply-migration-042.ts @@ -0,0 +1,37 @@ +/** + * Aplica la migración 042 (ncs_emitidas + ncs_recibidas) a todos los tenants. + * Idempotente — ADD COLUMN IF NOT EXISTS no falla si ya existe. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { migrate } from '../src/config/tenant-migrations.js'; + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true, nombre: true }, + orderBy: { rfc: 'asc' }, + }); + + console.log(`Aplicando migraciones a ${tenants.length} tenants...\n`); + + let ok = 0; + let failed = 0; + + for (const t of tenants) { + try { + const pool = await tenantDb.getPool(t.id, t.databaseName); + await migrate(pool); + console.log(`✓ ${t.rfc.padEnd(25)} ${t.nombre}`); + ok++; + } catch (err: any) { + console.error(`✗ ${t.rfc.padEnd(25)} ${t.nombre} → ${err.message || err}`); + failed++; + } + } + + console.log(`\nCompletado: ${ok} OK, ${failed} fallidos`); + await prisma.$disconnect(); + process.exit(failed > 0 ? 1 : 0); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/backfill-cfdi-contribuyente.ts b/apps/api/scripts/backfill-cfdi-contribuyente.ts new file mode 100644 index 0000000..242f421 --- /dev/null +++ b/apps/api/scripts/backfill-cfdi-contribuyente.ts @@ -0,0 +1,158 @@ +/** + * Backfill de cfdis.contribuyente_id para los despachos. + * + * Asocia CFDIs huérfanos (contribuyente_id NULL) con el contribuyente cuyo RFC + * coincide con rfc_emisor (si type='EMITIDO') o rfc_receptor (si type='RECIBIDO'). + * + * Causa raíz: retry path de sat.service.ts construía SyncContext sin + * contribuyenteId (bug fixed 2026-04-20). + * + * Idempotente: solo actualiza filas con contribuyente_id IS NULL y match único + * por RFC. Si no hay contribuyentes en el tenant (Horux360 clásico), no-op. + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/backfill-cfdi-contribuyente.ts # ejecuta + * pnpm --filter @horux/api exec tsx scripts/backfill-cfdi-contribuyente.ts --dry # reporta sin escribir + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run'); + +interface PerTenantResult { + tenantId: string; + rfc: string; + databaseName: string; + contribuyentesCount: number; + updated: number; + perContribuyente: Array<{ rfc: string; entidadId: string; rows: number }>; + error?: string; +} + +async function backfillTenant( + tenantId: string, + rfc: string, + databaseName: string, +): Promise { + const result: PerTenantResult = { + tenantId, + rfc, + databaseName, + contribuyentesCount: 0, + updated: 0, + perContribuyente: [], + }; + + const pool = await tenantDb.getPool(tenantId, databaseName); + + const { rows: contribs } = await pool.query<{ entidad_id: string; rfc: string }>( + `SELECT entidad_id, rfc FROM contribuyentes`, + ); + result.contribuyentesCount = contribs.length; + if (contribs.length === 0) return result; + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + const sql = ` + UPDATE cfdis c + SET contribuyente_id = cnt.entidad_id + FROM contribuyentes cnt + WHERE c.contribuyente_id IS NULL + AND ( + (c.type = 'EMITIDO' AND cnt.rfc = c.rfc_emisor) OR + (c.type = 'RECIBIDO' AND cnt.rfc = c.rfc_receptor) + ) + RETURNING cnt.entidad_id as "entidadId", cnt.rfc as "rfcContrib" + `; + + const { rows: updated } = await client.query<{ entidadId: string; rfcContrib: string }>(sql); + result.updated = updated.length; + + const byContrib = new Map(); + for (const row of updated) { + const cur = byContrib.get(row.entidadId); + if (cur) cur.rows += 1; + else byContrib.set(row.entidadId, { rfc: row.rfcContrib, rows: 1 }); + } + result.perContribuyente = Array.from(byContrib.entries()).map(([entidadId, v]) => ({ + entidadId, + rfc: v.rfc, + rows: v.rows, + })); + + if (DRY_RUN) { + await client.query('ROLLBACK'); + } else { + await client.query('COMMIT'); + } + } catch (err: any) { + await client.query('ROLLBACK').catch(() => {}); + result.error = err?.message || String(err); + } finally { + client.release(); + } + + return result; +} + +async function main() { + console.log(`=== Backfill cfdis.contribuyente_id ${DRY_RUN ? '(DRY RUN — no writes)' : ''} ===\n`); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + orderBy: { rfc: 'asc' }, + }); + + console.log(`Tenants activos: ${tenants.length}\n`); + + const results: PerTenantResult[] = []; + for (const t of tenants) { + process.stdout.write(`[${t.rfc}] (${t.databaseName}) ... `); + try { + const r = await backfillTenant(t.id, t.rfc, t.databaseName); + results.push(r); + if (r.error) { + console.log(`ERROR: ${r.error}`); + } else if (r.contribuyentesCount === 0) { + console.log(`sin contribuyentes (skip)`); + } else { + console.log(`${r.contribuyentesCount} contribs, ${r.updated} CFDIs backfill`); + for (const pc of r.perContribuyente) { + console.log(` ${pc.rfc}: ${pc.rows}`); + } + } + } catch (err: any) { + console.log(`FATAL: ${err?.message || err}`); + results.push({ + tenantId: t.id, + rfc: t.rfc, + databaseName: t.databaseName, + contribuyentesCount: 0, + updated: 0, + perContribuyente: [], + error: err?.message || String(err), + }); + } + } + + const totalUpdated = results.reduce((s, r) => s + r.updated, 0); + const tenantsTouched = results.filter(r => r.updated > 0).length; + const tenantsFailed = results.filter(r => r.error).length; + + console.log(`\n=== Resumen ===`); + console.log(` Tenants procesados: ${results.length}`); + console.log(` Tenants con backfill: ${tenantsTouched}`); + console.log(` CFDIs actualizados: ${totalUpdated}${DRY_RUN ? ' (rolled back)' : ''}`); + if (tenantsFailed > 0) console.log(` Tenants con error: ${tenantsFailed}`); + + await prisma.$disconnect(); + process.exit(tenantsFailed > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/backfill-cfdis-relaciones.ts b/apps/api/scripts/backfill-cfdis-relaciones.ts new file mode 100644 index 0000000..082705e --- /dev/null +++ b/apps/api/scripts/backfill-cfdis-relaciones.ts @@ -0,0 +1,209 @@ +/** + * Backfill de cfdis.cfdi_tipo_relacion + cfdis.cfdis_relacionados desde + * xml_original para CFDIs pre-migración 032. + * + * Criterio: WHERE xml_original IS NOT NULL AND cfdi_tipo_relacion IS NULL. + * Re-usa `parseXml()` para mantener la lógica de extracción idéntica al sync. + * Solo escribe si el parser extrae `cfdiTipoRelacion` no-nulo — los CFDIs sin + * CfdiRelacionados se siguen dejando con NULL (distinguible de "no procesado" + * via el filtro `cfdi_tipo_relacion IS NULL` porque el WHERE al final del run + * ya no los va a volver a tocar — pero cada invocación empieza desde el mismo + * filtro, por eso es idempotente: los sin-relación se re-parsean cada vez pero + * no se escribe nada). + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/backfill-cfdis-relaciones.ts # ejecuta + * pnpm --filter @horux/api exec tsx scripts/backfill-cfdis-relaciones.ts --dry # reporta sin escribir + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { parseXml } from '../src/services/sat/sat-parser.service.js'; + +const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run'); + +interface PerTenantResult { + tenantId: string; + rfc: string; + databaseName: string; + scanned: number; + parsedOk: number; + parseFailed: number; + withRelation: number; + updated: number; + byTipoRelacion: Record; + error?: string; +} + +async function backfillTenant( + tenantId: string, + rfc: string, + databaseName: string, +): Promise { + const result: PerTenantResult = { + tenantId, + rfc, + databaseName, + scanned: 0, + parsedOk: 0, + parseFailed: 0, + withRelation: 0, + updated: 0, + byTipoRelacion: {}, + }; + + const pool = await tenantDb.getPool(tenantId, databaseName); + + const { rows } = await pool.query<{ + id: number; + uuid: string; + type: string; + xml_original: string | null; + }>( + `SELECT id, uuid, type, xml_original + FROM cfdis + WHERE xml_original IS NOT NULL + AND cfdi_tipo_relacion IS NULL + ORDER BY id`, + ); + + result.scanned = rows.length; + if (rows.length === 0) return result; + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + for (const row of rows) { + if (!row.xml_original) continue; + + const downloadType = row.type === 'EMITIDO' ? 'emitidos' : 'recibidos'; + let parsed; + try { + parsed = parseXml(row.xml_original, downloadType); + } catch { + result.parseFailed++; + continue; + } + + if (!parsed) { + result.parseFailed++; + continue; + } + result.parsedOk++; + + if (!parsed.cfdiTipoRelacion) continue; + + result.withRelation++; + const tr = parsed.cfdiTipoRelacion; + result.byTipoRelacion[tr] = (result.byTipoRelacion[tr] || 0) + 1; + + await client.query( + `UPDATE cfdis + SET cfdi_tipo_relacion = $2, + cfdis_relacionados = $3, + actualizado_en = NOW() + WHERE id = $1`, + [row.id, parsed.cfdiTipoRelacion, parsed.cfdisRelacionados], + ); + result.updated++; + } + + if (DRY_RUN) { + await client.query('ROLLBACK'); + } else { + await client.query('COMMIT'); + } + } catch (err: any) { + await client.query('ROLLBACK').catch(() => {}); + result.error = err?.message || String(err); + } finally { + client.release(); + } + + return result; +} + +async function main() { + console.log(`=== Backfill cfdis CfdiRelacionados ${DRY_RUN ? '(DRY RUN — no writes)' : ''} ===\n`); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + orderBy: { rfc: 'asc' }, + }); + + console.log(`Tenants activos: ${tenants.length}\n`); + + const results: PerTenantResult[] = []; + for (const t of tenants) { + process.stdout.write(`[${t.rfc}] (${t.databaseName}) ... `); + try { + const r = await backfillTenant(t.id, t.rfc, t.databaseName); + results.push(r); + if (r.error) { + console.log(`ERROR: ${r.error}`); + } else if (r.scanned === 0) { + console.log(`sin CFDIs candidatos (skip)`); + } else { + const tiposStr = Object.entries(r.byTipoRelacion) + .sort((a, b) => b[1] - a[1]) + .map(([tr, n]) => `${tr}:${n}`) + .join(', '); + console.log( + `scan=${r.scanned} parsed=${r.parsedOk} fail=${r.parseFailed} rel=${r.withRelation} upd=${r.updated}${ + tiposStr ? ` [${tiposStr}]` : '' + }`, + ); + } + } catch (err: any) { + console.log(`FATAL: ${err?.message || err}`); + results.push({ + tenantId: t.id, + rfc: t.rfc, + databaseName: t.databaseName, + scanned: 0, + parsedOk: 0, + parseFailed: 0, + withRelation: 0, + updated: 0, + byTipoRelacion: {}, + error: err?.message || String(err), + }); + } + } + + const totalScanned = results.reduce((s, r) => s + r.scanned, 0); + const totalUpdated = results.reduce((s, r) => s + r.updated, 0); + const totalParseFailed = results.reduce((s, r) => s + r.parseFailed, 0); + const tenantsTouched = results.filter(r => r.updated > 0).length; + const tenantsFailed = results.filter(r => r.error).length; + + const tiposGlobales: Record = {}; + for (const r of results) { + for (const [tr, n] of Object.entries(r.byTipoRelacion)) { + tiposGlobales[tr] = (tiposGlobales[tr] || 0) + n; + } + } + + console.log(`\n=== Resumen ===`); + console.log(` Tenants procesados: ${results.length}`); + console.log(` Tenants con backfill: ${tenantsTouched}`); + console.log(` CFDIs escaneados: ${totalScanned}`); + console.log(` CFDIs actualizados: ${totalUpdated}${DRY_RUN ? ' (rolled back)' : ''}`); + if (totalParseFailed > 0) console.log(` CFDIs parse falló: ${totalParseFailed}`); + if (tenantsFailed > 0) console.log(` Tenants con error: ${tenantsFailed}`); + if (Object.keys(tiposGlobales).length > 0) { + console.log(` Desglose TipoRelacion:`); + for (const [tr, n] of Object.entries(tiposGlobales).sort((a, b) => b[1] - a[1])) { + console.log(` ${tr}: ${n}`); + } + } + + await prisma.$disconnect(); + process.exit(tenantsFailed > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/backfill-facturapi-cfdis.ts b/apps/api/scripts/backfill-facturapi-cfdis.ts new file mode 100644 index 0000000..fd403e8 --- /dev/null +++ b/apps/api/scripts/backfill-facturapi-cfdis.ts @@ -0,0 +1,126 @@ +/** + * Backfill one-shot: completa los campos de emisor/subtotal/IVA/XML en las + * filas de `cfdis` con `source='facturapi'` que fueron insertadas por la + * versión buggy del controller (previo al fix 2026-04-24). + * + * Descarga el XML real de Facturapi, lo parsea con el mismo parser SAT, + * upsertea la fila de `rfcs` del emisor, y actualiza la fila de `cfdis`. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { downloadXmlContribuyente } from '../src/services/contribuyente-facturapi.service.js'; +import * as facturapiService from '../src/services/facturapi.service.js'; +import { parseXml } from '../src/services/sat/sat-parser.service.js'; + +const TENANT_RFC = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: TENANT_RFC }, + select: { id: true, databaseName: true }, + }); + if (!tenant) { + console.log(`Tenant ${TENANT_RFC} no encontrado`); + return; + } + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const { rows: pendientes } = await pool.query<{ + id: number; + uuid: string; + facturapi_id: string; + contribuyente_id: string | null; + rfc_emisor: string | null; + }>( + `SELECT id, uuid, facturapi_id, contribuyente_id, rfc_emisor + FROM cfdis + WHERE source = 'facturapi' + AND (COALESCE(rfc_emisor, '') = '' OR xml_original IS NULL OR subtotal = 0) + ORDER BY fecha_emision ASC`, + ); + + console.log(`Encontradas ${pendientes.length} CFDIs Facturapi a backfillear en ${TENANT_RFC}\n`); + + let ok = 0; + let fail = 0; + + for (const row of pendientes) { + try { + console.log(`\n[${row.uuid}] facturapi_id=${row.facturapi_id} contrib=${row.contribuyente_id}`); + + const xmlBuffer = row.contribuyente_id + ? await downloadXmlContribuyente(pool, row.contribuyente_id, row.facturapi_id) + : await facturapiService.downloadXml(tenant.id, row.facturapi_id); + const xmlString = xmlBuffer.toString('utf-8'); + const parsed = parseXml(xmlString, 'emitidos'); + if (!parsed) { + console.log(` ⚠️ Parser retornó null — skip`); + fail++; + continue; + } + + console.log(` emisor=${parsed.rfcEmisor} (${parsed.nombreEmisor}, régimen ${parsed.regimenFiscalEmisor})`); + console.log(` receptor=${parsed.rfcReceptor} (${parsed.nombreReceptor}, régimen ${parsed.regimenFiscalReceptor})`); + console.log(` subtotal=${parsed.subtotal} total=${parsed.total} iva_traslado=${parsed.ivaTraslado}`); + + // Upsert rfcs emisor + const { rows: [emisorRow] } = await pool.query( + `INSERT INTO rfcs (rfc, razon_social, regimen_fiscal) VALUES ($1, $2, $3) + ON CONFLICT (rfc) DO UPDATE SET + razon_social = COALESCE(NULLIF($2, ''), rfcs.razon_social), + regimen_fiscal = CASE WHEN $3 IS NOT NULL AND $3 != '' THEN $3 ELSE rfcs.regimen_fiscal END + RETURNING id`, + [parsed.rfcEmisor, parsed.nombreEmisor || null, parsed.regimenFiscalEmisor || null], + ); + // Upsert rfcs receptor + const { rows: [receptorRow] } = await pool.query( + `INSERT INTO rfcs (rfc, razon_social, regimen_fiscal) VALUES ($1, $2, $3) + ON CONFLICT (rfc) DO UPDATE SET + razon_social = COALESCE(NULLIF($2, ''), rfcs.razon_social), + regimen_fiscal = CASE WHEN $3 IS NOT NULL AND $3 != '' THEN $3 ELSE rfcs.regimen_fiscal END + RETURNING id`, + [parsed.rfcReceptor, parsed.nombreReceptor || null, parsed.regimenFiscalReceptor || null], + ); + + await pool.query( + `UPDATE cfdis SET + fecha_cert_sat = $2, + rfc_emisor_id = $3, rfc_emisor = $4, nombre_emisor = $5, + regimen_fiscal_emisor = $6, + rfc_receptor_id = $7, rfc_receptor = $8, nombre_receptor = $9, + regimen_fiscal_receptor = $10, + subtotal = $11, subtotal_mxn = $11, + total = $12, total_mxn = $12, + iva_traslado = $13, iva_traslado_mxn = $13, + iva_retencion = $14, iva_retencion_mxn = $14, + xml_original = $15, + serie = COALESCE($16, serie), folio = COALESCE($17, folio) + WHERE id = $1`, + [ + row.id, + parsed.fechaCertSat, + emisorRow.id, parsed.rfcEmisor, parsed.nombreEmisor, + parsed.regimenFiscalEmisor, + receptorRow.id, parsed.rfcReceptor, parsed.nombreReceptor, + parsed.regimenFiscalReceptor, + parsed.subtotal, + parsed.total, + parsed.ivaTraslado, + parsed.ivaRetencion, + xmlString, + parsed.serie, parsed.folio, + ], + ); + + console.log(` ✅ actualizada fila id=${row.id}`); + ok++; + } catch (e: any) { + console.log(` ❌ error: ${e?.message || String(e)}`); + fail++; + } + } + + console.log(`\n=== Resumen: ${ok} actualizadas, ${fail} fallidas ===`); + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/backfill-fechas-tz.ts b/apps/api/scripts/backfill-fechas-tz.ts new file mode 100644 index 0000000..b7016b7 --- /dev/null +++ b/apps/api/scripts/backfill-fechas-tz.ts @@ -0,0 +1,174 @@ +/** + * Backfill de `fecha_emision` (y opcionalmente `fecha_cert_sat`) para CFDIs + * sincronizados antes del fix de zona horaria. El parser convertía la fecha + * del XML ("2025-12-31T18:37:51") asumiéndola como hora local de la máquina + * y la guardaba en UTC ("2026-01-01T00:37:51Z"), corriendo 6 horas y a veces + * sacando el CFDI de su mes/año correcto. + * + * Re-parsea la fecha literal del XML (atributo `Fecha=""` del Comprobante y + * `FechaTimbrado=""` del TimbreFiscalDigital) y lo guarda como UTC-literal + * (forzando 'Z' al string del XML). + * + * Solo aplica a CFDIs con `xml_original IS NOT NULL`. Idempotente. + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/backfill-fechas-tz.ts # ejecuta + * pnpm --filter @horux/api exec tsx scripts/backfill-fechas-tz.ts --dry # reporta + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run'); + +function parseLiteral(str: string | null | undefined): Date | null { + if (!str) return null; + const s = String(str).trim(); + if (!s) return null; + const hasTz = /[Zz]|[+-]\d{2}:?\d{2}$/.test(s); + return new Date(hasTz ? s : s + 'Z'); +} + +function extractFechaFromXml(xml: string): string | null { + // Atributo Fecha del root + const m = xml.match(/]*\bFecha="([^"]+)"/); + return m ? m[1] : null; +} + +function extractFechaTimbradoFromXml(xml: string): string | null { + const m = xml.match(/]*\bFechaTimbrado="([^"]+)"/); + return m ? m[1] : null; +} + +interface PerTenantResult { + tenantId: string; + rfc: string; + databaseName: string; + scanned: number; + updatedFechaEmision: number; + updatedFechaCert: number; + noChange: number; + noXmlMatch: number; + error?: string; +} + +async function backfillTenant(tenantId: string, rfc: string, databaseName: string): Promise { + const result: PerTenantResult = { + tenantId, rfc, databaseName, + scanned: 0, updatedFechaEmision: 0, updatedFechaCert: 0, noChange: 0, noXmlMatch: 0, + }; + const pool = await tenantDb.getPool(tenantId, databaseName); + + const { rows } = await pool.query<{ + id: number; + uuid: string; + fecha_emision: Date; + fecha_cert_sat: Date | null; + xml_original: string; + }>( + `SELECT id, uuid, fecha_emision, fecha_cert_sat, xml_original + FROM cfdis + WHERE xml_original IS NOT NULL + ORDER BY id`, + ); + result.scanned = rows.length; + if (rows.length === 0) return result; + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + for (const row of rows) { + const fechaXml = extractFechaFromXml(row.xml_original); + const fechaTimbradoXml = extractFechaTimbradoFromXml(row.xml_original); + + if (!fechaXml) { + result.noXmlMatch++; + continue; + } + + const nuevaFecha = parseLiteral(fechaXml); + const nuevaFechaCert = fechaTimbradoXml ? parseLiteral(fechaTimbradoXml) : null; + + if (!nuevaFecha) { + result.noXmlMatch++; + continue; + } + + const fechaEmisionActual = row.fecha_emision?.toISOString(); + const fechaCertActual = row.fecha_cert_sat?.toISOString(); + const fechaEmisionNueva = nuevaFecha.toISOString(); + const fechaCertNueva = nuevaFechaCert?.toISOString(); + + let updatedThis = false; + if (fechaEmisionActual !== fechaEmisionNueva) { + await client.query( + `UPDATE cfdis SET fecha_emision = $2 WHERE id = $1`, + [row.id, nuevaFecha], + ); + result.updatedFechaEmision++; + updatedThis = true; + } + if (nuevaFechaCert && fechaCertActual !== fechaCertNueva) { + await client.query( + `UPDATE cfdis SET fecha_cert_sat = $2 WHERE id = $1`, + [row.id, nuevaFechaCert], + ); + result.updatedFechaCert++; + updatedThis = true; + } + if (!updatedThis) result.noChange++; + } + + if (DRY_RUN) await client.query('ROLLBACK'); + else await client.query('COMMIT'); + } catch (err: any) { + await client.query('ROLLBACK').catch(() => {}); + result.error = err?.message || String(err); + } finally { + client.release(); + } + + return result; +} + +async function main() { + console.log(`=== Backfill fechas (fecha_emision + fecha_cert_sat) ${DRY_RUN ? '(DRY RUN)' : ''} ===\n`); + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + orderBy: { rfc: 'asc' }, + }); + console.log(`Tenants activos: ${tenants.length}\n`); + + const results: PerTenantResult[] = []; + for (const t of tenants) { + process.stdout.write(`[${t.rfc}] ... `); + try { + const r = await backfillTenant(t.id, t.rfc, t.databaseName); + results.push(r); + if (r.error) console.log(`ERROR: ${r.error}`); + else if (r.scanned === 0) console.log(`sin XMLs (skip)`); + else console.log( + `scan=${r.scanned} upd_emision=${r.updatedFechaEmision} upd_cert=${r.updatedFechaCert} ` + + `sin_cambio=${r.noChange} sin_match=${r.noXmlMatch}${DRY_RUN ? ' (rolled back)' : ''}`, + ); + } catch (err: any) { + console.log(`FATAL: ${err?.message || err}`); + } + } + + const totalScan = results.reduce((s, r) => s + r.scanned, 0); + const totalUpdEm = results.reduce((s, r) => s + r.updatedFechaEmision, 0); + const totalUpdCert = results.reduce((s, r) => s + r.updatedFechaCert, 0); + const tFail = results.filter(r => r.error).length; + + console.log(`\n=== Resumen ===`); + console.log(` Tenants procesados: ${results.length}`); + console.log(` CFDIs escaneados: ${totalScan}`); + console.log(` fecha_emision actualizada: ${totalUpdEm}`); + console.log(` fecha_cert_sat actualizada: ${totalUpdCert}`); + if (tFail > 0) console.log(` Tenants con error: ${tFail}`); + + await prisma.$disconnect(); + process.exit(tFail > 0 ? 1 : 0); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/backfill-metricas.ts b/apps/api/scripts/backfill-metricas.ts new file mode 100644 index 0000000..1b6e818 --- /dev/null +++ b/apps/api/scripts/backfill-metricas.ts @@ -0,0 +1,101 @@ +/** + * Backfill de métricas mensuales pre-calculadas (Tanda A hot/cold). + * + * Itera todos los tenants activos, sus contribuyentes, y popula la tabla + * `metricas_mensuales` con los agregados de años pasados (desde el CFDI más + * antiguo hasta el año actual - 1). El año actual queda on-the-fly. + * + * Idempotente: usa upsert — re-correrlo no duplica filas, recalcula valores. + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/backfill-metricas.ts # ejecuta + * pnpm --filter @horux/api exec tsx scripts/backfill-metricas.ts --dry # dry-run + * + * Opciones via env: + * BACKFILL_DESDE_ANIO=2023 # limita el rango inferior + * BACKFILL_HASTA_ANIO=2024 # default: año actual - 1 + * BACKFILL_TENANT= # procesa solo un tenant + */ +import { prisma } from '../src/config/database.js'; +import { backfillTenant } from '../src/services/metricas-compute.service.js'; + +const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run'); +const TENANT_FILTER = process.env.BACKFILL_TENANT || null; +const DESDE_ANIO = process.env.BACKFILL_DESDE_ANIO ? parseInt(process.env.BACKFILL_DESDE_ANIO, 10) : undefined; +const HASTA_ANIO = process.env.BACKFILL_HASTA_ANIO ? parseInt(process.env.BACKFILL_HASTA_ANIO, 10) : undefined; + +async function main() { + console.log(`=== Backfill metricas_mensuales ${DRY_RUN ? '(DRY RUN)' : ''} ===\n`); + if (DESDE_ANIO) console.log(`Desde año: ${DESDE_ANIO}`); + if (HASTA_ANIO) console.log(`Hasta año: ${HASTA_ANIO}`); + if (TENANT_FILTER) console.log(`Tenant filtro: ${TENANT_FILTER}`); + console.log(); + + const tenants = await prisma.tenant.findMany({ + where: { + active: true, + ...(TENANT_FILTER ? { id: TENANT_FILTER } : {}), + }, + select: { id: true, rfc: true, nombre: true }, + orderBy: { rfc: 'asc' }, + }); + + console.log(`Tenants activos: ${tenants.length}\n`); + + let totalContribs = 0; + let totalMeses = 0; + let totalFilas = 0; + let totalErrores = 0; + + for (const t of tenants) { + process.stdout.write(`[${t.rfc}] ${t.nombre} ... `); + try { + const r = await backfillTenant(t.id, { + dryRun: DRY_RUN, + desdeAnio: DESDE_ANIO, + hastaAnio: HASTA_ANIO, + }); + if (r.contribuyentesProcesados === 0) { + console.log('sin contribuyentes (skip)'); + } else { + console.log( + `${r.contribuyentesProcesados} contribs, ${r.mesesProcesados} meses, ` + + `${r.filasEscritas} filas${r.errores.length > 0 ? `, ${r.errores.length} errores` : ''}`, + ); + if (r.errores.length > 0 && r.errores.length <= 5) { + for (const e of r.errores) { + console.log(` ERR (${e.anio}-${String(e.mes).padStart(2, '0')}): ${e.error}`); + } + } else if (r.errores.length > 5) { + console.log(` (${r.errores.length} errores — los primeros 3):`); + for (const e of r.errores.slice(0, 3)) { + console.log(` ERR (${e.anio}-${String(e.mes).padStart(2, '0')}): ${e.error}`); + } + } + } + totalContribs += r.contribuyentesProcesados; + totalMeses += r.mesesProcesados; + totalFilas += r.filasEscritas; + totalErrores += r.errores.length; + } catch (err: any) { + console.log(`FATAL: ${err?.message || err}`); + totalErrores++; + } + } + + console.log(`\n=== Resumen ===`); + console.log(` Tenants procesados: ${tenants.length}`); + console.log(` Contribuyentes: ${totalContribs}`); + console.log(` (Contribuyente, mes): ${totalMeses}`); + console.log(` Filas metricas_mensuales: ${totalFilas}${DRY_RUN ? ' (NO escritas)' : ''}`); + if (totalErrores > 0) console.log(` Errores: ${totalErrores}`); + + await prisma.$disconnect(); + process.exit(totalErrores > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/backfill-pago-fields.ts b/apps/api/scripts/backfill-pago-fields.ts new file mode 100644 index 0000000..9f79d92 --- /dev/null +++ b/apps/api/scripts/backfill-pago-fields.ts @@ -0,0 +1,78 @@ +/** + * Backfill: re-parsea CFDIs tipo P emitidos vía Facturapi (source='facturapi') + * que tienen `monto_pago_mxn` o `fecha_pago_p` NULL, y popula esos campos + * desde el XML original. Bug histórico — el INSERT de facturapi.controller.ts + * no incluía los campos del complemento Pagos hasta el fix de hoy. + * + * Idempotente — solo actualiza si el XML tiene datos y el row tiene NULL. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { parseXml } from '../src/services/sat/sat-parser.service.js'; + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, nombre: true, databaseName: true }, + }); + + let totalUpdated = 0; + let totalChecked = 0; + + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows } = await pool.query(` + SELECT uuid, xml_original + FROM cfdis + WHERE source = 'facturapi' + AND tipo_comprobante = 'P' + AND xml_original IS NOT NULL + AND (monto_pago_mxn IS NULL OR fecha_pago_p IS NULL) + `); + + if (rows.length === 0) continue; + console.log(`\n>>> ${t.rfc} (${t.nombre}): ${rows.length} P por backfill`); + + for (const r of rows) { + totalChecked++; + const parsed = parseXml(r.xml_original, 'emitidos'); + if (!parsed || parsed.tipoComprobante !== 'P') continue; + + const fechaPagoP = parsed.fechaPagoP + ? new Date(String(parsed.fechaPagoP).split('|')[0]) + : null; + + if (!parsed.montoPago && !fechaPagoP) { + console.log(` ${r.uuid}: XML sin datos de Pago — skip`); + continue; + } + + await pool.query(` + UPDATE cfdis SET + monto_pago = COALESCE(monto_pago, $1), + monto_pago_mxn = COALESCE(monto_pago_mxn, $1), + fecha_pago_p = COALESCE(fecha_pago_p, $2), + iva_traslado_pago = COALESCE(iva_traslado_pago, $3), + iva_traslado_pago_mxn = COALESCE(iva_traslado_pago_mxn, $3), + iva_retencion_pago = COALESCE(iva_retencion_pago, $4), + iva_retencion_pago_mxn = COALESCE(iva_retencion_pago_mxn, $4), + ieps_traslado_pago = COALESCE(ieps_traslado_pago, $5), + ieps_traslado_pago_mxn = COALESCE(ieps_traslado_pago_mxn, $5) + WHERE uuid = $6 + `, [ + parsed.montoPago || 0, + fechaPagoP, + parsed.ivaTrasladoPago || 0, + parsed.ivaRetencionPago || 0, + parsed.iepsTrasladoPago || 0, + r.uuid, + ]); + totalUpdated++; + console.log(` ${r.uuid}: ✓ monto=$${parsed.montoPago} fecha_pago=${fechaPagoP?.toISOString().slice(0, 10)} iva=$${parsed.ivaTrasladoPago}`); + } + } + + console.log(`\n[Backfill] Completado: ${totalUpdated}/${totalChecked} actualizadas`); + await prisma.$disconnect(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/backfill-saldo-pendiente.ts b/apps/api/scripts/backfill-saldo-pendiente.ts new file mode 100644 index 0000000..90c72a5 --- /dev/null +++ b/apps/api/scripts/backfill-saldo-pendiente.ts @@ -0,0 +1,163 @@ +/** + * Backfill de `saldo_pendiente_mxn` para CFDIs I PPD vigentes. Computa el + * saldo con la fórmula centralizada en `utils/saldo.ts` (pagos P + NC no-07 + * + anticipo aplicado si es I/07) y lo persiste. + * + * Idempotente: corrido varias veces produce el mismo resultado. Safe para + * repetir después de un sync SAT masivo o si se sospecha drift. + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/backfill-saldo-pendiente.ts # ejecuta + * pnpm --filter @horux/api exec tsx scripts/backfill-saldo-pendiente.ts --dry # reporta sin escribir + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { saldoComputadoExpr } from '../src/utils/saldo.js'; + +const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run'); + +interface PerTenantResult { + tenantId: string; + rfc: string; + databaseName: string; + iPpdsVigentes: number; + actualizadas: number; + saldoTotalAntes: number; + saldoTotalDespues: number; + error?: string; +} + +async function backfillTenant( + tenantId: string, + rfc: string, + databaseName: string, +): Promise { + const result: PerTenantResult = { + tenantId, + rfc, + databaseName, + iPpdsVigentes: 0, + actualizadas: 0, + saldoTotalAntes: 0, + saldoTotalDespues: 0, + }; + + const pool = await tenantDb.getPool(tenantId, databaseName); + + const { rows: count } = await pool.query<{ n: number; suma: string }>( + `SELECT COUNT(*)::int AS n, COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) AS suma + FROM cfdis + WHERE tipo_comprobante = 'I' AND metodo_pago = 'PPD' + AND status NOT IN ('Cancelado', '0')`, + ); + result.iPpdsVigentes = count[0]?.n || 0; + result.saldoTotalAntes = Number(count[0]?.suma || 0); + if (result.iPpdsVigentes === 0) return result; + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + // UPDATE masivo con la fórmula centralizada (misma que hooks y reporte). + const expr = saldoComputadoExpr('c'); + const { rowCount } = await client.query( + `UPDATE cfdis c + SET saldo_pendiente_mxn = ${expr} + WHERE c.tipo_comprobante = 'I' + AND c.metodo_pago = 'PPD' + AND c.status NOT IN ('Cancelado', '0')`, + ); + result.actualizadas = rowCount ?? 0; + + const { rows: cntDespues } = await client.query<{ suma: string }>( + `SELECT COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) AS suma + FROM cfdis + WHERE tipo_comprobante = 'I' AND metodo_pago = 'PPD' + AND status NOT IN ('Cancelado', '0')`, + ); + result.saldoTotalDespues = Number(cntDespues[0]?.suma || 0); + + if (DRY_RUN) { + await client.query('ROLLBACK'); + } else { + await client.query('COMMIT'); + } + } catch (err: any) { + await client.query('ROLLBACK').catch(() => {}); + result.error = err?.message || String(err); + } finally { + client.release(); + } + + return result; +} + +function fmt(n: number): string { + return n.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +} + +async function main() { + console.log(`=== Backfill saldo_pendiente_mxn ${DRY_RUN ? '(DRY RUN — no writes)' : ''} ===\n`); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + orderBy: { rfc: 'asc' }, + }); + + console.log(`Tenants activos: ${tenants.length}\n`); + + const results: PerTenantResult[] = []; + for (const t of tenants) { + process.stdout.write(`[${t.rfc}] ... `); + try { + const r = await backfillTenant(t.id, t.rfc, t.databaseName); + results.push(r); + if (r.error) { + console.log(`ERROR: ${r.error}`); + } else if (r.iPpdsVigentes === 0) { + console.log(`sin I PPD vigentes (skip)`); + } else { + const delta = r.saldoTotalDespues - r.saldoTotalAntes; + console.log( + `I_PPD=${r.iPpdsVigentes} upd=${r.actualizadas} ` + + `antes=${fmt(r.saldoTotalAntes)} despues=${fmt(r.saldoTotalDespues)} ` + + `Δ=${delta >= 0 ? '+' : ''}${fmt(delta)}${DRY_RUN ? ' (rolled back)' : ''}`, + ); + } + } catch (err: any) { + console.log(`FATAL: ${err?.message || err}`); + results.push({ + tenantId: t.id, + rfc: t.rfc, + databaseName: t.databaseName, + iPpdsVigentes: 0, + actualizadas: 0, + saldoTotalAntes: 0, + saldoTotalDespues: 0, + error: err?.message || String(err), + }); + } + } + + const totalI = results.reduce((s, r) => s + r.iPpdsVigentes, 0); + const totalAntes = results.reduce((s, r) => s + r.saldoTotalAntes, 0); + const totalDespues = results.reduce((s, r) => s + r.saldoTotalDespues, 0); + const tenantsFailed = results.filter(r => r.error).length; + + console.log(`\n=== Resumen ===`); + console.log(` Tenants procesados: ${results.length}`); + console.log(` I PPD vigentes total: ${totalI}`); + console.log(` Saldo total antes: ${fmt(totalAntes)}`); + console.log(` Saldo total después: ${fmt(totalDespues)}${DRY_RUN ? ' (rolled back)' : ''}`); + console.log(` Delta (recuperado): ${fmt(totalAntes - totalDespues)} (saldo que ya no está pendiente)`); + if (tenantsFailed > 0) console.log(` Tenants con error: ${tenantsFailed}`); + + await prisma.$disconnect(); + process.exit(tenantsFailed > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/bootstrap-horux360-admin.ts b/apps/api/scripts/bootstrap-horux360-admin.ts new file mode 100644 index 0000000..8034d9e --- /dev/null +++ b/apps/api/scripts/bootstrap-horux360-admin.ts @@ -0,0 +1,131 @@ +/** + * Bootstrap del tenant admin global (Horux 360 — HTS240708LJA) + usuarios staff. + * + * Crea: + * 1. Tenant Horux 360 (RFC HTS240708LJA, plan enterprise) + * 2. Carlos como owner del tenant + rol platform_admin + * 3. Ivan como contador del tenant + rol platform_ti (TI superset) + * 4. Suscripción authorized por 1 año + * + * Uso: `pnpm bootstrap:admin-global` + * + * Idempotente-ish: falla limpio si el tenant ya existe (RFC unique). + * Para re-ejecutar, borra el tenant y su BD manualmente antes. + * + * Requisitos previos: + * 1. `pnpm prisma migrate deploy` (schema central) + * 2. `pnpm db:seed` (catálogos SAT, regímenes, ISR, eventos fiscales, roles) + * + * Env vars opcionales (con defaults): + * HORUX_ADMIN_EMAIL (default: carlos@horuxfin.com) + * HORUX_ADMIN_NOMBRE (default: Carlos) + * HORUX_TI_EMAIL (default: ivan@horuxfin.com) + * HORUX_TI_NOMBRE (default: Ivan) + */ +import { prisma } from '../src/config/database.js'; +import * as tenantsService from '../src/services/tenants.service.js'; +import * as usuariosService from '../src/services/usuarios.service.js'; + +const RFC = 'HTS240708LJA'; +const TENANT_NAME = 'Horux 360'; +const PLAN = 'custom' as const; +const SUBSCRIPTION_YEARS = 1; + +async function main() { + const adminEmail = process.env.HORUX_ADMIN_EMAIL || 'carlos@horuxfin.com'; + const adminNombre = process.env.HORUX_ADMIN_NOMBRE || 'Carlos'; + const tiEmail = process.env.HORUX_TI_EMAIL || 'ivan@horuxfin.com'; + const tiNombre = process.env.HORUX_TI_NOMBRE || 'Ivan'; + + console.log(`Bootstrap del tenant admin global`); + console.log(` RFC: ${RFC}`); + console.log(` Nombre: ${TENANT_NAME}`); + console.log(` Admin: ${adminNombre} <${adminEmail}> (platform_admin)`); + console.log(` TI: ${tiNombre} <${tiEmail}> (platform_ti)`); + console.log(` Plan: ${PLAN} (sin cobro — admin global)`); + console.log(''); + + // 1. Crea tenant + BD provisionada + Carlos como owner + subscription pending + const { tenant, user: carlosUser, tempPassword: carlosPassword } = await tenantsService.createTenant({ + nombre: TENANT_NAME, + rfc: RFC, + plan: PLAN, + adminEmail, + adminNombre, + amount: 0, + }); + + console.log(`✓ Tenant creado: ${tenant.id}`); + console.log(`✓ BD provisionada: ${tenant.databaseName}`); + console.log(`✓ Carlos creado (owner): ${carlosUser.email}`); + + // 2. Asigna platform_admin a Carlos (no se hace automáticamente desde tenants.service) + const carlosFull = await prisma.user.findUnique({ where: { email: adminEmail } }); + if (carlosFull) { + await prisma.userPlatformRole.upsert({ + where: { userId_role: { userId: carlosFull.id, role: 'platform_admin' } }, + update: {}, + create: { userId: carlosFull.id, role: 'platform_admin' }, + }); + console.log(`✓ Carlos: rol platform_admin asignado`); + } + + // 3. Crea Ivan como contador del tenant (membership) y le asigna platform_ti + const ivan = await usuariosService.inviteUsuario(tenant.id, { + email: tiEmail, + nombre: tiNombre, + role: 'contador', + }); + console.log(`✓ Ivan creado: ${ivan.email} (membership contador)`); + + await prisma.userPlatformRole.upsert({ + where: { userId_role: { userId: ivan.id, role: 'platform_ti' } }, + update: {}, + create: { userId: ivan.id, role: 'platform_ti' }, + }); + console.log(`✓ Ivan: rol platform_ti asignado (superset, mismos permisos que admin)`); + + // 4. Sube la subscription a 'authorized' con vigencia de 1 año + const existing = await prisma.subscription.findFirst({ + where: { tenantId: tenant.id }, + orderBy: { createdAt: 'desc' }, + }); + if (existing) { + const now = new Date(); + const end = new Date(now); + end.setFullYear(end.getFullYear() + SUBSCRIPTION_YEARS); + + await prisma.subscription.update({ + where: { id: existing.id }, + data: { + status: 'authorized', + currentPeriodStart: now, + currentPeriodEnd: end, + }, + }); + console.log(`✓ Suscripción marcada 'authorized' hasta ${end.toISOString().slice(0, 10)}`); + } + + console.log(''); + console.log('=== DONE ==='); + console.log(`Credenciales temporales para primer login:`); + console.log(` Carlos (admin): ${adminEmail}`); + console.log(` Password: ${carlosPassword}`); + console.log(''); + console.log(` Ivan (TI): ${tiEmail}`); + console.log(` Password: revisa el correo de bienvenida (inviteUsuario lo envía por email)`); + console.log(''); + console.log('Próximos pasos manuales:'); + console.log(` 1. Carlos login en /login con las credenciales de arriba`); + console.log(` 2. Cambiar el password desde /configuracion/seguridad`); + console.log(` 3. Verificar que Ivan recibió su correo de invitación`); + console.log(` 4. Subir FIEL en /configuracion/sat para habilitar sincronización`); + console.log(` 5. (Opcional) Configurar organización Facturapi en /configuracion`); +} + +main() + .catch((err) => { + console.error('✗ Bootstrap falló:', err.message || err); + process.exit(1); + }) + .finally(() => prisma.$disconnect()); diff --git a/apps/api/scripts/breakdown-gastos.ts b/apps/api/scripts/breakdown-gastos.ts new file mode 100644 index 0000000..6c3b127 --- /dev/null +++ b/apps/api/scripts/breakdown-gastos.ts @@ -0,0 +1,75 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const yearMonth = '2025-02'; +const contribuyenteId = 'd745a915-6a23-4818-944b-a7e1e18e536a'; +const tenantRfc = 'DESPACHO_MO3NI6U8_B9VGG'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const [anio, mes] = yearMonth.split('-').map(Number); + const lastDay = new Date(anio, mes, 0).getDate(); + const fi = `${yearMonth}-01`; + const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`; + + const IMP_TRAS = `COALESCE(iva_traslado_mxn,0) + COALESCE(ieps_traslado_mxn,0) + COALESCE(impuestos_locales_trasladado_mxn,0)`; + const IMP_TRAS_PAGO = `COALESCE(iva_traslado_pago_mxn,0) + COALESCE(ieps_traslado_pago_mxn,0)`; + const EXCL_MONTO = `COALESCE((SELECT SUM(COALESCE(cc.importe_mxn,0) - COALESCE(cc.descuento_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ('84121603','93161608','85101501','85121800')), 0)`; + + // Drill desglosado por régimen del receptor + const { rows } = await pool.query( + `SELECT + COALESCE(regimen_fiscal_receptor, 'null') AS regimen_rec, + type, tipo_comprobante, metodo_pago, + COALESCE(cfdi_tipo_relacion, '') AS tipo_rel, + COUNT(*)::int AS n, + SUM(total_mxn) AS total_bruto, + SUM(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})) AS total_neto, + SUM(COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO})) AS pago_neto + FROM cfdis + WHERE ( + (type='RECIBIDO' AND tipo_comprobante='I' AND metodo_pago='PUE') + OR (type='RECIBIDO' AND tipo_comprobante='P') + OR (type='RECIBIDO' AND tipo_comprobante='E' AND metodo_pago='PUE' AND COALESCE(cfdi_tipo_relacion,'')<>'07') + ) + AND status NOT IN ('Cancelado','0') + AND ((tipo_comprobante='P' AND fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')) + OR (tipo_comprobante!='P' AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day'))) + AND contribuyente_id = $3 + GROUP BY regimen_rec, type, tipo_comprobante, metodo_pago, tipo_rel + ORDER BY regimen_rec, tipo_comprobante, metodo_pago`, + [fi, ff, contribuyenteId], + ); + + const byReg: Record = {}; + for (const r of rows) { + const reg = r.regimen_rec; + if (!byReg[reg]) byReg[reg] = { fact: 0, pago: 0, nc: 0, detalle: [] }; + const v = r.tipo_comprobante === 'P' ? Number(r.pago_neto) : Number(r.total_neto); + byReg[reg].detalle.push({ tc: r.tipo_comprobante, mp: r.metodo_pago, rel: r.tipo_rel, n: r.n, valor: v, bruto: Number(r.total_bruto) }); + if (r.tipo_comprobante === 'I') byReg[reg].fact += v; + else if (r.tipo_comprobante === 'P') byReg[reg].pago += v; + else if (r.tipo_comprobante === 'E') byReg[reg].nc += v; + } + + console.log(`\n=== DRILL-DOWN por régimen del receptor — ${fi} a ${ff} ===\n`); + let totalAll = 0; + const TODOS_REGS = new Set(['605','606','612','621','625','626','601','603','607','608','610','611','614','615','620','622','623','624']); + for (const [reg, v] of Object.entries(byReg).sort()) { + const subtot = v.fact + v.pago - v.nc; + totalAll += subtot; + const inTodos = TODOS_REGS.has(reg) ? '✓' : '✗ (excluido de TODOS_REGIMENES)'; + console.log(`Régimen ${reg} ${inTodos}`); + console.log(` fact=${v.fact.toFixed(2)} pago=${v.pago.toFixed(2)} NC=${v.nc.toFixed(2)} → subtotal=${subtot.toFixed(2)}`); + for (const d of v.detalle) { + console.log(` ${d.tc} ${d.mp || '-'} rel=${d.rel || '-'} n=${d.n} bruto=${d.bruto.toFixed(2)} neto=${d.valor.toFixed(2)}`); + } + } + console.log(`\nTotal todos regímenes: ${totalAll.toFixed(2)}`); + const inTodos = Object.entries(byReg).filter(([r]) => TODOS_REGS.has(r)).reduce((s, [, v]) => s + (v.fact + v.pago - v.nc), 0); + console.log(`Total solo en TODOS_REGIMENES: ${inTodos.toFixed(2)}`); + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/breakdown-ingresos.ts b/apps/api/scripts/breakdown-ingresos.ts new file mode 100644 index 0000000..3e6f3ee --- /dev/null +++ b/apps/api/scripts/breakdown-ingresos.ts @@ -0,0 +1,67 @@ +/** + * Breakdown ingresos por grupo + filas que el drill-down mostraría, + * para un contribuyente + mes. Identifica discrepancias entre el + * dashboard y el drill. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { resolveContribuyenteContext } from '../src/utils/contribuyente-context.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'b3761db6-0b8d-4251-8078-4ddc31e9c75b'; +const yearMonth = process.argv[4] || '2025-05'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const [anio, mes] = yearMonth.split('-').map(Number); + const lastDay = new Date(anio, mes, 0).getDate(); + const fi = `${yearMonth}-01`; + const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`; + + const ctx = await resolveContribuyenteContext(pool, tenant.id, contribuyenteId); + console.log(`\n=== ${yearMonth} ${contribuyenteId} RFC=${ctx.rfc} ===\n`); + console.log(`esEmisor: ${ctx.esEmisor}`); + console.log(`esReceptor: ${ctx.esReceptor}\n`); + + // Todos los CFDIs donde el contribuyente es emisor en el mes (ingresos potenciales) + const { rows: emitidos } = await pool.query( + `SELECT uuid, fecha_emision, tipo_comprobante, metodo_pago, + cfdi_tipo_relacion, regimen_fiscal_emisor, regimen_fiscal_receptor, + total_mxn, monto_pago_mxn + FROM cfdis + WHERE ${ctx.esEmisor} + AND status NOT IN ('Cancelado', '0') + AND ((tipo_comprobante='P' AND fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')) + OR (tipo_comprobante<>'P' AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day'))) + ORDER BY fecha_emision, uuid`, + [fi, ff], + ); + + console.log(`EMITIDOS por el contribuyente en el mes: ${emitidos.length}`); + let sumaTotal = 0, sumaPagos = 0; + const porRegimen: Record }> = {}; + for (const r of emitidos) { + const reg = r.regimen_fiscal_emisor || 'NULL'; + const tcKey = `${r.tipo_comprobante}${r.metodo_pago ? '/' + r.metodo_pago : ''}${r.cfdi_tipo_relacion ? '/rel=' + r.cfdi_tipo_relacion : ''}`; + if (!porRegimen[reg]) porRegimen[reg] = { n: 0, total: 0, pago: 0, types: {} }; + porRegimen[reg].n++; + porRegimen[reg].total += Number(r.total_mxn || 0); + porRegimen[reg].pago += Number(r.monto_pago_mxn || 0); + porRegimen[reg].types[tcKey] = (porRegimen[reg].types[tcKey] || 0) + 1; + sumaTotal += Number(r.total_mxn || 0); + sumaPagos += Number(r.monto_pago_mxn || 0); + } + + console.log(`Suma total_mxn: ${sumaTotal.toFixed(2)} | Suma monto_pago_mxn: ${sumaPagos.toFixed(2)}\n`); + for (const [reg, v] of Object.entries(porRegimen)) { + console.log(` Régimen ${reg}: n=${v.n} total=${v.total.toFixed(2)} pago=${v.pago.toFixed(2)}`); + for (const [tc, n] of Object.entries(v.types)) { + console.log(` ${tc}: ${n}`); + } + } + + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/check-cache-contrib.ts b/apps/api/scripts/check-cache-contrib.ts new file mode 100644 index 0000000..7456911 --- /dev/null +++ b/apps/api/scripts/check-cache-contrib.ts @@ -0,0 +1,24 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3]; +const year = process.argv[4] || '2025'; +const month = process.argv[5]; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + const monthFilter = month ? `AND mes = ${Number(month)}` : ''; + const { rows } = await pool.query( + `SELECT anio, mes, regimen_fiscal, ingresos_cobrados, egresos_pagados, + iva_trasladado_total, iva_acreditable, computed_at + FROM metricas_mensuales + WHERE contribuyente_id = $1 AND anio = $2 ${monthFilter} + ORDER BY mes, regimen_fiscal`, + [contribuyenteId, Number(year)], + ); + for (const r of rows) console.log(r); + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/check-cache.ts b/apps/api/scripts/check-cache.ts new file mode 100644 index 0000000..5cc8430 --- /dev/null +++ b/apps/api/scripts/check-cache.ts @@ -0,0 +1,26 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const { rows } = await pool.query( + `SELECT anio, mes, regimen_fiscal, + ingresos_cobrados, egresos_pagados, + iva_trasladado_total, iva_acreditable, + computed_at + FROM metricas_mensuales + WHERE contribuyente_id = $1 AND anio = 2025 AND mes = 2 + ORDER BY regimen_fiscal`, + ['d745a915-6a23-4818-944b-a7e1e18e536a'], + ); + console.log(`Cache rows para Feb 2025:`); + for (const r of rows) console.log(r); + + // Also force on-the-fly by setting BYPASS + process.env.METRICAS_BYPASS_CACHE = '1'; + console.log(`\n(cache bypassed below is N/A here; the dashboard service reads planCache directly)`); + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/check-carlos-emision.ts b/apps/api/scripts/check-carlos-emision.ts new file mode 100644 index 0000000..05150e9 --- /dev/null +++ b/apps/api/scripts/check-carlos-emision.ts @@ -0,0 +1,85 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const RFC_CARLOS = 'TORC9611214CA'; + +async function main() { + const tenants = await prisma.tenant.findMany({ + select: { id: true, rfc: true, databaseName: true }, + }); + + let found = false; + + for (const t of tenants) { + let pool; + try { + pool = await tenantDb.getPool(t.id, t.databaseName); + } catch { + continue; + } + + const { rows: contribs } = await pool.query( + `SELECT c.entidad_id, c.rfc, c.regimen_fiscal, e.nombre, fo.facturapi_org_id, fo.csd_uploaded, fo.active AS org_active + FROM contribuyentes c + JOIN entidades_gestionadas e ON e.id = c.entidad_id + LEFT JOIN facturapi_orgs fo ON fo.contribuyente_id = c.entidad_id + WHERE UPPER(c.rfc) = $1`, + [RFC_CARLOS], + ); + if (contribs.length === 0) continue; + + found = true; + console.log(`\n=== Tenant ${t.rfc} — BD ${t.databaseName} ===`); + for (const c of contribs) { + console.log(`Contribuyente Carlos: ${c.entidad_id}`); + console.log(` nombre=${c.nombre}`); + console.log(` regimen_fiscal (CSV)=${c.regimen_fiscal}`); + console.log(` facturapi_org_id=${c.facturapi_org_id || 'NULL (sin org)'}`); + console.log(` csd_uploaded=${c.csd_uploaded} org_active=${c.org_active}`); + } + + const { rows: cfdis } = await pool.query( + `SELECT uuid, type, tipo_comprobante, metodo_pago, total, total_mxn, + rfc_emisor, rfc_receptor, nombre_receptor, status, fecha_emision, + source, facturapi_id + FROM cfdis + WHERE UPPER(rfc_emisor) = $1 + AND (source = 'facturapi' OR facturapi_id IS NOT NULL OR fecha_emision >= NOW() - interval '2 days') + ORDER BY fecha_emision DESC + LIMIT 10`, + [RFC_CARLOS], + ); + + console.log(`\nÚltimas ${cfdis.length} facturas (facturapi o recientes) emitidas por ${RFC_CARLOS}:`); + for (const c of cfdis) { + console.log(` UUID=${c.uuid}`); + console.log(` tipo=${c.tipo_comprobante} mp=${c.metodo_pago} status=${c.status} source=${c.source}`); + console.log(` receptor=${c.rfc_receptor} (${c.nombre_receptor})`); + console.log(` total=${c.total} total_mxn=${c.total_mxn}`); + console.log(` fecha_emision=${c.fecha_emision?.toISOString?.() || c.fecha_emision}`); + console.log(` facturapi_id=${c.facturapi_id}`); + } + + const { rows: [anyEmitido] } = await pool.query( + `SELECT COUNT(*)::int AS total, + SUM(CASE WHEN source='facturapi' THEN 1 ELSE 0 END)::int AS via_facturapi, + SUM(CASE WHEN source='facturapi' AND status NOT IN ('Cancelado','0') THEN 1 ELSE 0 END)::int AS vigentes + FROM cfdis + WHERE UPPER(rfc_emisor) = $1`, + [RFC_CARLOS], + ); + console.log(`\nResumen total CFDIs con rfc_emisor=${RFC_CARLOS}:`); + console.log(` total=${anyEmitido.total} via_facturapi=${anyEmitido.via_facturapi} vigentes_facturapi=${anyEmitido.vigentes}`); + } + + if (!found) { + console.log(`\nNo se encontró contribuyente con RFC ${RFC_CARLOS} en ningún tenant.`); + } + + await prisma.$disconnect(); +} + +main().catch(async e => { + console.error(e); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/check-carlos-lco.ts b/apps/api/scripts/check-carlos-lco.ts new file mode 100644 index 0000000..657894e --- /dev/null +++ b/apps/api/scripts/check-carlos-lco.ts @@ -0,0 +1,72 @@ +import { prisma, tenantDb } from '../src/config/database.js'; +import { env } from '../src/config/env.js'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + // 1. Last CSF stored for Carlos (source of truth on what SAT sees) + const { rows: csfs } = await pool.query( + `SELECT rfc, created_at, datos->'regimenes' AS regimenes, datos->'obligaciones' AS obligaciones, + datos->>'estatusPadron' AS estatus, datos->>'fechaInicioOperaciones' AS fecha_inicio, + datos->'domicilio' AS domicilio + FROM constancias_situacion_fiscal + WHERE UPPER(rfc) = 'TORC9611214CA' + ORDER BY created_at DESC LIMIT 1`, + ); + console.log(`\n=== CSF más reciente de Carlos ===`); + if (csfs.length === 0) { + console.log('NO HAY CSF descargada para este RFC. Eso explica el error de LCO si el contribuyente no ha sincronizado con SAT.'); + } else { + const c = csfs[0]; + console.log(`created_at: ${c.created_at}`); + console.log(`estatusPadron: ${c.estatus}`); + console.log(`fechaInicioOper: ${c.fecha_inicio}`); + console.log(`Regímenes (CSF):`); + if (Array.isArray(c.regimenes)) for (const r of c.regimenes) console.log(' ', r); + console.log(`Obligaciones (CSF):`); + if (Array.isArray(c.obligaciones)) for (const o of c.obligaciones) console.log(' ', o); + } + + // 2. Contribuyente data en BD (lo que estamos usando para llenar la org) + const { rows: contrib } = await pool.query( + `SELECT c.entidad_id, c.rfc, r.razon_social, c.regimen_fiscal, c.codigo_postal, c.domicilio + FROM contribuyentes c + LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc) + WHERE UPPER(c.rfc) = 'TORC9611214CA'`, + ); + console.log(`\n=== Contribuyente en BD ===`); + console.log(contrib[0]); + + // 3. Facturapi org actual (lo que Facturapi está enviando al SAT) + const { rows: org } = await pool.query( + `SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true`, + [contrib[0]?.entidad_id], + ); + if (org.length > 0 && env.FACTURAPI_USER_KEY) { + const res = await fetch(`https://www.facturapi.io/v2/organizations/${org[0].facturapi_org_id}`, { + headers: { 'Authorization': `Bearer ${env.FACTURAPI_USER_KEY}` }, + }); + if (res.ok) { + const o = await res.json() as any; + console.log(`\n=== Facturapi Organization ===`); + console.log(`orgId: ${o.id}`); + console.log(`name: ${o.name}`); + console.log(`legal:`); + console.log(` legal_name: ${o.legal?.legal_name}`); + console.log(` tax_system: ${o.legal?.tax_system}`); + console.log(` name: ${o.legal?.name}`); + console.log(` address: ${JSON.stringify(o.legal?.address)}`); + console.log(`certificate:`); + console.log(` has_certificate: ${o.certificate?.has_certificate}`); + console.log(` serial_number: ${o.certificate?.serial_number}`); + console.log(` valid_until: ${o.certificate?.valid_until}`); + } else { + console.log(`Facturapi GET failed: ${res.status}`); + } + } + + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/check-ieps-inflation.ts b/apps/api/scripts/check-ieps-inflation.ts new file mode 100644 index 0000000..67bf18d --- /dev/null +++ b/apps/api/scripts/check-ieps-inflation.ts @@ -0,0 +1,112 @@ +/** + * Detecta complementos P cuya ieps_traslado_pago_mxn parece inflada + * respecto al monto pagado y respecto a la factura referenciada. + * + * Heurísticas: + * 1. IEPS del P > monto_pago × 1.6 (tasa máxima teórica SAT para bebidas + * con alto contenido alcohólico; cualquier cosa arriba es sospechoso). + * 2. IEPS del P > IEPS de la factura original a la que se refiere + * (imposible — un pago parcial no puede transferir más IEPS que el total). + * 3. Ratio IEPS / monto_pago vs IEPS_original / total_original, donde la + * proporción del P excede la del original por >5pp (señal de error + * del proveedor). + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenants = await prisma.tenant.findMany({ + select: { id: true, rfc: true, databaseName: true }, + }); + + for (const t of tenants) { + let pool; + try { + pool = await tenantDb.getPool(t.id, t.databaseName); + } catch { + continue; + } + + console.log(`\n=== Tenant ${t.rfc} (${t.databaseName}) ===`); + + // Heurística 1: IEPS > 160% del monto + const { rows: h1 } = await pool.query(` + SELECT uuid, rfc_emisor, rfc_receptor, monto_pago_mxn, ieps_traslado_pago_mxn, + (ieps_traslado_pago_mxn / NULLIF(monto_pago_mxn, 0))::numeric(10,4) AS ratio + FROM cfdis + WHERE tipo_comprobante = 'P' + AND status NOT IN ('Cancelado', '0') + AND COALESCE(ieps_traslado_pago_mxn, 0) > 0 + AND COALESCE(monto_pago_mxn, 0) > 0 + AND ieps_traslado_pago_mxn > monto_pago_mxn * 1.6 + ORDER BY ieps_traslado_pago_mxn DESC + LIMIT 10 + `); + console.log(`\n-- H1: IEPS > monto_pago × 1.6 (${h1.length}) --`); + for (const r of h1) { + console.log(` ${r.uuid.substring(0, 8)} ${r.rfc_emisor}→${r.rfc_receptor} pago=${Number(r.monto_pago_mxn).toFixed(2)} IEPS=${Number(r.ieps_traslado_pago_mxn).toFixed(2)} ratio=${r.ratio}`); + } + + // Heurística 2: IEPS del P > IEPS de la factura referenciada (imposible) + // uuid_relacionado es pipe-separated; normalizar + const { rows: h2 } = await pool.query(` + SELECT p.uuid AS p_uuid, p.rfc_emisor, p.monto_pago_mxn, p.ieps_traslado_pago_mxn, + i.uuid AS i_uuid, i.total_mxn AS i_total, i.ieps_traslado_mxn AS i_ieps + FROM cfdis p + JOIN cfdis i + ON LOWER(i.uuid) = ANY(string_to_array(LOWER(COALESCE(p.uuid_relacionado, '')), '|')) + AND i.status NOT IN ('Cancelado', '0') + WHERE p.tipo_comprobante = 'P' + AND p.status NOT IN ('Cancelado', '0') + AND COALESCE(p.ieps_traslado_pago_mxn, 0) > 0 + AND COALESCE(p.ieps_traslado_pago_mxn, 0) > COALESCE(i.ieps_traslado_mxn, 0) + ORDER BY p.ieps_traslado_pago_mxn DESC + LIMIT 10 + `); + console.log(`\n-- H2: IEPS del P > IEPS de la factura referenciada (${h2.length}) --`); + for (const r of h2) { + const ratio = r.i_ieps > 0 ? Number(r.ieps_traslado_pago_mxn) / Number(r.i_ieps) : 0; + console.log(` P=${r.p_uuid.substring(0, 8)} IEPS_P=${Number(r.ieps_traslado_pago_mxn).toFixed(2)} I=${r.i_uuid.substring(0, 8)} IEPS_I=${Number(r.i_ieps || 0).toFixed(2)} ratio=${ratio.toFixed(2)}x`); + } + + // Heurística 3: ratio IEPS/pago del P muy distinto del ratio IEPS/total del I + const { rows: h3 } = await pool.query(` + SELECT p.uuid AS p_uuid, p.monto_pago_mxn, p.ieps_traslado_pago_mxn, + i.uuid AS i_uuid, i.total_mxn AS i_total, i.ieps_traslado_mxn AS i_ieps, + (p.ieps_traslado_pago_mxn / NULLIF(p.monto_pago_mxn, 0))::numeric(6,4) AS ratio_p, + (i.ieps_traslado_mxn / NULLIF(i.total_mxn, 0))::numeric(6,4) AS ratio_i + FROM cfdis p + JOIN cfdis i + ON LOWER(i.uuid) = ANY(string_to_array(LOWER(COALESCE(p.uuid_relacionado, '')), '|')) + AND i.status NOT IN ('Cancelado', '0') + WHERE p.tipo_comprobante = 'P' + AND p.status NOT IN ('Cancelado', '0') + AND COALESCE(p.ieps_traslado_pago_mxn, 0) > 0 + AND COALESCE(i.ieps_traslado_mxn, 0) > 0 + AND COALESCE(p.monto_pago_mxn, 0) > 0 + AND COALESCE(i.total_mxn, 0) > 0 + AND ABS( + (p.ieps_traslado_pago_mxn / p.monto_pago_mxn) + - (i.ieps_traslado_mxn / i.total_mxn) + ) > 0.05 + ORDER BY p.ieps_traslado_pago_mxn DESC + LIMIT 10 + `); + console.log(`\n-- H3: ratio_P − ratio_I > 5pp (${h3.length}) --`); + for (const r of h3) { + console.log(` P=${r.p_uuid.substring(0, 8)} ratio_P=${r.ratio_p} I=${r.i_uuid.substring(0, 8)} ratio_I=${r.ratio_i} delta=${(Number(r.ratio_p) - Number(r.ratio_i)).toFixed(4)}`); + } + + // Resumen: total de P con IEPS > 0 + const { rows: [summary] } = await pool.query(` + SELECT COUNT(*) FILTER (WHERE COALESCE(ieps_traslado_pago_mxn, 0) > 0)::int AS p_con_ieps, + COUNT(*) FILTER (WHERE tipo_comprobante = 'P')::int AS p_total + FROM cfdis + WHERE status NOT IN ('Cancelado', '0') + `); + console.log(`\nResumen: ${summary.p_con_ieps} P con IEPS > 0 (de ${summary.p_total} P totales)`); + } + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/check-recent-facturapi.ts b/apps/api/scripts/check-recent-facturapi.ts new file mode 100644 index 0000000..f801445 --- /dev/null +++ b/apps/api/scripts/check-recent-facturapi.ts @@ -0,0 +1,76 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const TENANT_RFC = 'DESPACHO_MO3NI6U8_B9VGG'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: TENANT_RFC }, + select: { id: true, databaseName: true }, + }); + if (!tenant) { + console.log('Tenant no encontrado'); + return; + } + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + console.log(`\n=== Tenant ${TENANT_RFC} ===\n`); + + // 1) CFDIs emitidos via Facturapi (cualquier emisor) últimos 7 días + console.log(`>> CFDIs con source='facturapi' o facturapi_id no nulo, últimos 7 días:`); + const { rows: recientes } = await pool.query( + `SELECT uuid, rfc_emisor, rfc_receptor, nombre_receptor, tipo_comprobante, metodo_pago, + total, total_mxn, status, fecha_emision, source, facturapi_id + FROM cfdis + WHERE (source = 'facturapi' OR facturapi_id IS NOT NULL) + AND fecha_emision >= NOW() - interval '7 days' + ORDER BY fecha_emision DESC + LIMIT 20`, + ); + if (recientes.length === 0) console.log(' (ninguno)'); + for (const r of recientes) { + const emisor = r.rfc_emisor || ''; + const receptor = r.rfc_receptor || ''; + console.log(` ${r.uuid}`); + console.log(` EMISOR=${emisor} RECEPTOR=${receptor} (${r.nombre_receptor})`); + console.log(` tipo=${r.tipo_comprobante}/${r.metodo_pago} total=${r.total} status=${r.status} source=${r.source}`); + console.log(` fecha_emision=${r.fecha_emision?.toISOString?.() || r.fecha_emision}`); + console.log(` facturapi_id=${r.facturapi_id}`); + } + + // 2) CFDIs totales en últimas 2 horas (cualquier emisor, cualquier source) + console.log(`\n>> CFDIs insertados en últimas 2 horas (cualquier source):`); + const { rows: ultimas } = await pool.query( + `SELECT uuid, rfc_emisor, rfc_receptor, tipo_comprobante, total, + status, fecha_emision, source, facturapi_id + FROM cfdis + WHERE fecha_emision >= NOW() - interval '2 hours' + ORDER BY fecha_emision DESC + LIMIT 20`, + ); + if (ultimas.length === 0) console.log(' (ninguno)'); + for (const r of ultimas) { + console.log(` ${r.uuid} | ${r.rfc_emisor} → ${r.rfc_receptor}`); + console.log(` tipo=${r.tipo_comprobante} total=${r.total} status=${r.status} source=${r.source}`); + console.log(` facturapi_id=${r.facturapi_id || 'null'}`); + } + + // 3) Distribución de source en toda la BD + console.log(`\n>> Distribución de 'source' en cfdis:`); + const { rows: dist } = await pool.query( + `SELECT source, COUNT(*)::int AS cnt + FROM cfdis + GROUP BY source + ORDER BY cnt DESC`, + ); + for (const r of dist) { + console.log(` source=${r.source || 'NULL'} → ${r.cnt}`); + } + + await prisma.$disconnect(); +} + +main().catch(async e => { + console.error(e); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/check-rfc-emisor.ts b/apps/api/scripts/check-rfc-emisor.ts new file mode 100644 index 0000000..c0f9b81 --- /dev/null +++ b/apps/api/scripts/check-rfc-emisor.ts @@ -0,0 +1,36 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' }, + select: { id: true, databaseName: true }, + }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const { rows } = await pool.query( + `SELECT * FROM rfcs WHERE id IN (23709, 1) ORDER BY id`, + ); + for (const r of rows) { + console.log(`\nrfcs id=${r.id}:`); + for (const k of Object.keys(r).sort()) { + console.log(` ${k} = ${r[k]}`); + } + } + + // Also look at all 4 Facturapi CFDIs' emisor fields + const { rows: all4 } = await pool.query( + `SELECT uuid, rfc_emisor, nombre_emisor, rfc_emisor_id, regimen_fiscal_emisor, + rfc_receptor, nombre_receptor, subtotal, total, xml_original IS NULL AS no_xml + FROM cfdis WHERE source='facturapi' ORDER BY fecha_emision DESC`, + ); + console.log(`\n=== Todas las CFDIs source=facturapi (${all4.length}) ===`); + for (const r of all4) { + console.log(` ${r.uuid} | emisor='${r.rfc_emisor}' (id=${r.rfc_emisor_id}, nombre='${r.nombre_emisor}', regimen=${r.regimen_fiscal_emisor})`); + console.log(` receptor='${r.rfc_receptor}' (${r.nombre_receptor}) subtotal=${r.subtotal} total=${r.total} xml_missing=${r.no_xml}`); + } + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/check-saldo.ts b/apps/api/scripts/check-saldo.ts new file mode 100644 index 0000000..5934aa9 --- /dev/null +++ b/apps/api/scripts/check-saldo.ts @@ -0,0 +1,63 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const uuid = (process.argv[2] || '5c874749-748f-11f0-96b1-2b9310891836').toLowerCase(); + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows } = await pool.query( + `SELECT + c.uuid, c.total_mxn, + COALESCE(( + SELECT SUM(COALESCE(p.monto_pago_mxn, 0)) + FROM cfdis p + WHERE p.tipo_comprobante = 'P' + AND LOWER(COALESCE(p.uuid_relacionado, '')) LIKE '%' || LOWER(c.uuid) || '%' + AND p.status NOT IN ('Cancelado', '0') + ), 0) AS pagos_p, + COALESCE(( + SELECT SUM(COALESCE(e.total_mxn, 0)) + FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND COALESCE(e.cfdi_tipo_relacion, '') <> '07' + AND e.cfdis_relacionados IS NOT NULL + AND LOWER(c.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND e.status NOT IN ('Cancelado', '0') + ), 0) AS ncs, + CASE WHEN c.cfdi_tipo_relacion = '07' AND c.cfdis_relacionados IS NOT NULL THEN + COALESCE(( + SELECT SUM(COALESCE(a.total_mxn, 0)) + FROM cfdis a + WHERE LOWER(a.uuid) = ANY(string_to_array(LOWER(c.cfdis_relacionados), '|')) + AND a.status NOT IN ('Cancelado', '0') + ), 0) ELSE 0 END AS anticipo_aplicado, + ( + COALESCE(c.total_mxn, 0) + - COALESCE((SELECT SUM(COALESCE(p.monto_pago_mxn, 0)) FROM cfdis p + WHERE p.tipo_comprobante = 'P' + AND LOWER(COALESCE(p.uuid_relacionado, '')) LIKE '%' || LOWER(c.uuid) || '%' + AND p.status NOT IN ('Cancelado', '0')), 0) + - COALESCE((SELECT SUM(COALESCE(e.total_mxn, 0)) FROM cfdis e + WHERE e.tipo_comprobante = 'E' AND COALESCE(e.cfdi_tipo_relacion,'') <> '07' + AND e.cfdis_relacionados IS NOT NULL + AND LOWER(c.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND e.status NOT IN ('Cancelado','0')), 0) + - CASE WHEN c.cfdi_tipo_relacion = '07' AND c.cfdis_relacionados IS NOT NULL THEN + COALESCE((SELECT SUM(COALESCE(a.total_mxn,0)) FROM cfdis a + WHERE LOWER(a.uuid) = ANY(string_to_array(LOWER(c.cfdis_relacionados),'|')) + AND a.status NOT IN ('Cancelado','0')), 0) + ELSE 0 END + ) AS saldo_computado + FROM cfdis c WHERE LOWER(c.uuid) = $1`, + [uuid], + ); + if (rows.length === 0) continue; + console.log(`[${t.rfc}]`, rows[0]); + } + await prisma.$disconnect(); +} +main().catch(async (e) => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/compare-iva-full.ts b/apps/api/scripts/compare-iva-full.ts new file mode 100644 index 0000000..0138872 --- /dev/null +++ b/apps/api/scripts/compare-iva-full.ts @@ -0,0 +1,37 @@ +process.env.METRICAS_BYPASS_CACHE = '1'; +import { prisma, tenantDb } from '../src/config/database.js'; +import { calcularIngresosPorRegimen, calcularEgresosPorRegimen } from '../src/services/dashboard.service.js'; +import { getResumenIva } from '../src/services/impuestos.service.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'd745a915-6a23-4818-944b-a7e1e18e536a'; +const año = Number(process.argv[4] || '2025'); + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + console.log(`\n=== IVA trasladado/acreditable vs ingresos/gastos — ${año} contrib=${contribuyenteId} ===\n`); + console.log('Mes | Ingresos | IVA tras | Ratio | Gastos | IVA acred | Ratio '); + + for (let m = 1; m <= 12; m++) { + const lastDay = new Date(año, m, 0).getDate(); + const mm = String(m).padStart(2, '0'); + const fi = `${año}-${mm}-01`; + const ff = `${año}-${mm}-${String(lastDay).padStart(2, '0')}`; + + const [ing, gas, iva] = await Promise.all([ + calcularIngresosPorRegimen(pool, tenant.id, fi, ff, undefined, undefined, false, contribuyenteId), + calcularEgresosPorRegimen(pool, tenant.id, fi, ff, undefined, undefined, false, contribuyenteId), + getResumenIva(pool, fi, ff, tenant.id, false, contribuyenteId), + ]); + const rTras = ing.total > 0 ? (iva.trasladado / ing.total) * 100 : 0; + const rAcr = gas.total > 0 ? (iva.acreditable / gas.total) * 100 : 0; + const flagT = Math.abs(rTras - 16) > 3 && ing.total > 0 ? '⚠️' : ''; + const flagA = Math.abs(rAcr - 16) > 3 && gas.total > 0 ? '⚠️' : ''; + console.log(`${mm} | ${ing.total.toFixed(2).padStart(12)} | ${iva.trasladado.toFixed(2).padStart(13)} | ${rTras.toFixed(1).padStart(5)}%${flagT} | ${gas.total.toFixed(2).padStart(12)} | ${iva.acreditable.toFixed(2).padStart(13)} | ${rAcr.toFixed(1).padStart(5)}%${flagA}`); + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/compare-iva-gastos.ts b/apps/api/scripts/compare-iva-gastos.ts new file mode 100644 index 0000000..2ad42fb --- /dev/null +++ b/apps/api/scripts/compare-iva-gastos.ts @@ -0,0 +1,36 @@ +process.env.METRICAS_BYPASS_CACHE = '1'; +import { prisma, tenantDb } from '../src/config/database.js'; +import { calcularEgresosPorRegimen } from '../src/services/dashboard.service.js'; +import { getResumenIva } from '../src/services/impuestos.service.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'd745a915-6a23-4818-944b-a7e1e18e536a'; +const año = Number(process.argv[4] || '2025'); + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + console.log(`\n=== IVA acreditable vs Gastos por mes — ${año} contrib=${contribuyenteId} ===\n`); + console.log('Mes | Gastos | IVA acreditable | Ratio | Esperado (16%) | Diff'); + + for (let m = 1; m <= 12; m++) { + const lastDay = new Date(año, m, 0).getDate(); + const mm = String(m).padStart(2, '0'); + const fi = `${año}-${mm}-01`; + const ff = `${año}-${mm}-${String(lastDay).padStart(2, '0')}`; + + const [gastos, iva] = await Promise.all([ + calcularEgresosPorRegimen(pool, tenant.id, fi, ff, undefined, undefined, false, contribuyenteId), + getResumenIva(pool, fi, ff, tenant.id, false, contribuyenteId), + ]); + const ratio = gastos.total > 0 ? (iva.acreditable / gastos.total) * 100 : 0; + const esperado = gastos.total * 0.16; + const diff = iva.acreditable - esperado; + const flag = Math.abs(ratio - 16) > 3 && gastos.total > 0 ? ' ⚠️' : ''; + console.log(`${mm} | ${gastos.total.toFixed(2).padStart(13)} | ${iva.acreditable.toFixed(2).padStart(15)} | ${ratio.toFixed(2)}% | ${esperado.toFixed(2).padStart(13)} | ${diff.toFixed(2)}${flag}`); + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/count-07-types.ts b/apps/api/scripts/count-07-types.ts new file mode 100644 index 0000000..f4bb22f --- /dev/null +++ b/apps/api/scripts/count-07-types.ts @@ -0,0 +1,22 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenants = await prisma.tenant.findMany({ select: { id: true, rfc: true, databaseName: true } }); + for (const t of tenants) { + let pool; + try { pool = await tenantDb.getPool(t.id, t.databaseName); } catch { continue; } + console.log(`\n=== ${t.rfc} ===`); + const { rows } = await pool.query(` + SELECT tipo_comprobante, metodo_pago, COUNT(*)::int AS cnt + FROM cfdis + WHERE cfdi_tipo_relacion = '07' AND status NOT IN ('Cancelado','0') + GROUP BY tipo_comprobante, metodo_pago + ORDER BY cnt DESC + `); + for (const r of rows) { + console.log(` ${r.tipo_comprobante}/${r.metodo_pago || 'null'}: ${r.cnt}`); + } + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/count-husberto-07.ts b/apps/api/scripts/count-husberto-07.ts new file mode 100644 index 0000000..a403648 --- /dev/null +++ b/apps/api/scripts/count-husberto-07.ts @@ -0,0 +1,27 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const RFC = 'TOAH680201RA2'; + +async function main() { + const tenants = await prisma.tenant.findMany({ select: { id: true, rfc: true, databaseName: true } }); + for (const t of tenants) { + let pool; + try { pool = await tenantDb.getPool(t.id, t.databaseName); } catch { continue; } + const { rows } = await pool.query(` + SELECT tipo_comprobante, metodo_pago, cfdi_tipo_relacion, COUNT(*)::int AS cnt + FROM cfdis + WHERE (UPPER(rfc_emisor) = $1 OR UPPER(rfc_receptor) = $1) + AND status NOT IN ('Cancelado','0') + AND cfdi_tipo_relacion IS NOT NULL + GROUP BY tipo_comprobante, metodo_pago, cfdi_tipo_relacion + ORDER BY cnt DESC`, + [RFC]); + if (rows.length === 0) continue; + console.log(`\n=== ${t.rfc} (${RFC}) ===`); + for (const r of rows) { + console.log(` ${r.tipo_comprobante}/${r.metodo_pago || '?'}/rel=${r.cfdi_tipo_relacion}: ${r.cnt}`); + } + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/create-carlos.ts b/apps/api/scripts/create-carlos.ts new file mode 100644 index 0000000..fd7c5da --- /dev/null +++ b/apps/api/scripts/create-carlos.ts @@ -0,0 +1,26 @@ +import { prisma } from '../src/config/database.js'; +import { hashPassword } from '../src/utils/password.js'; + +async function main() { + const ivan = await prisma.user.findUnique({ where: { email: 'ivan@horuxfin.com' }, include: { tenant: true } }); + if (!ivan) { console.error('Ivan not found'); process.exit(1); } + + console.log('Tenant:', ivan.tenant.nombre, '(', ivan.tenant.id, ')'); + + const existing = await prisma.user.findUnique({ where: { email: 'carlos@horuxfin.com' } }); + if (existing) { console.log('Carlos already exists:', existing.id); process.exit(0); } + + const hash = await hashPassword('Aasi940812'); + const carlos = await prisma.user.create({ + data: { + tenantId: ivan.tenantId, + email: 'carlos@horuxfin.com', + passwordHash: hash, + nombre: 'Carlos Horux', + role: 'admin', + } + }); + console.log('Carlos created:', carlos.id, carlos.email, carlos.role); +} + +main().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/debug-cfdi-activos.ts b/apps/api/scripts/debug-cfdi-activos.ts new file mode 100644 index 0000000..ed3bd58 --- /dev/null +++ b/apps/api/scripts/debug-cfdi-activos.ts @@ -0,0 +1,103 @@ +/** + * Inspecciona un CFDI específico para entender por qué el filtro de + * "Considerar activos" no lo captura. Imprime los campos relevantes y + * cualquier CFDI relacionado. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const uuid = process.argv[2] || '8ec2eaf3-7879-11f0-81a8-8daae9822b10'; + + // Buscar en TODOS los tenants + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, nombre: true, databaseName: true }, + }); + + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows } = await pool.query(` + SELECT + uuid, type, tipo_comprobante, metodo_pago, forma_pago, + uso_cfdi, cfdi_tipo_relacion, + rfc_emisor, nombre_emisor, regimen_fiscal_emisor, + rfc_receptor, nombre_receptor, regimen_fiscal_receptor, + total_mxn, monto_pago_mxn, + fecha_emision, fecha_pago_p, status, + uuid_relacionado, cfdis_relacionados + FROM cfdis + WHERE uuid = $1 + `, [uuid]); + + if (rows.length === 0) continue; + + console.log(`\n═══ Tenant: ${t.rfc} (${t.nombre}) ═══`); + const r = rows[0]; + for (const [k, v] of Object.entries(r)) { + console.log(` ${k.padEnd(28)} ${v}`); + } + + // Si hay uuid_relacionado o cfdis_relacionados, traer esos también + if (r.uuid_relacionado) { + const { rows: rel } = await pool.query( + `SELECT uuid, tipo_comprobante, uso_cfdi, total_mxn FROM cfdis WHERE LOWER(uuid) = LOWER($1)`, + [r.uuid_relacionado], + ); + console.log(`\n Relacionado vía uuid_relacionado (${r.uuid_relacionado}):`); + console.log(rel[0] || '(no encontrado)'); + } + + if (r.cfdis_relacionados) { + const uuids = String(r.cfdis_relacionados).split('|').map(s => s.trim()).filter(Boolean); + console.log(`\n Relacionados vía cfdis_relacionados (${uuids.length}):`); + for (const u of uuids) { + const { rows: rel } = await pool.query( + `SELECT uuid, tipo_comprobante, uso_cfdi, total_mxn FROM cfdis WHERE LOWER(uuid) = LOWER($1)`, + [u], + ); + console.log(` ${u} →`, rel[0] || '(no encontrado)'); + } + } + + // Test del filtro: aplica activosExclusionNoAlias y verifica + const ACTIVOS_USOS = "('I01','I02','I03','I04','I05','I06','I07','I08')"; + const test = await pool.query(` + SELECT + (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS_USOS}) AS regla1_directo, + (tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS_USOS} + )) AS regla2_p_paga_activo, + (tipo_comprobante = 'E' AND cfdis.cfdis_relacionados IS NOT NULL AND EXISTS ( + SELECT 1 FROM cfdis r_act + WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|')) + AND (r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS_USOS}) + )) AS regla3_e_referencia_activo, + (tipo_comprobante = 'I' AND EXISTS ( + SELECT 1 FROM cfdis i07_act + WHERE i07_act.tipo_comprobante = 'I' + AND i07_act.metodo_pago = 'PPD' + AND COALESCE(i07_act.cfdi_tipo_relacion, '') = '07' + AND i07_act.uso_cfdi IN ${ACTIVOS_USOS} + AND i07_act.status NOT IN ('Cancelado', '0') + AND i07_act.cfdis_relacionados IS NOT NULL + AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(i07_act.cfdis_relacionados), '|')) + )) AS regla4_anticipo_activo + FROM cfdis + WHERE uuid = $1 + `, [uuid]); + console.log(`\n Filtro activos:`); + console.log(` regla1 (I directo activo): ${test.rows[0].regla1_directo}`); + console.log(` regla2 (P paga I activo): ${test.rows[0].regla2_p_paga_activo}`); + console.log(` regla3 (E ref. I/P activo): ${test.rows[0].regla3_e_referencia_activo}`); + console.log(` regla4 (anticipo de I/07 act): ${test.rows[0].regla4_anticipo_activo}`); + const filtrado = test.rows[0].regla1_directo || test.rows[0].regla2_p_paga_activo || test.rows[0].regla3_e_referencia_activo || test.rows[0].regla4_anticipo_activo; + console.log(` → ${filtrado ? '🔴 FILTRADO (excluido del cálculo)' : '🟢 PASA (incluido en cálculo)'}`); + } + + await prisma.$disconnect(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/debug-compensacion-cfdi.ts b/apps/api/scripts/debug-compensacion-cfdi.ts new file mode 100644 index 0000000..4cc437e --- /dev/null +++ b/apps/api/scripts/debug-compensacion-cfdi.ts @@ -0,0 +1,154 @@ +/** + * Diseca cómo el CFDI 8ec2eaf3-7879-11f0-81a8-8daae9822b10 (P de pago $295,100, + * uuid_relacionado → I de activo I03) se compensa en el cálculo de deducciones + * de Husberto en agosto 2025, con considerarActivos=true vs false. + * + * Reproduce las queries reales de calcularEgresosPorRegimen para mostrar + * el aporte categoría por categoría. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const t = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' } }); + if (!t) { console.log('Patito tenant not found'); return; } + const pool = await tenantDb.getPool(t.id, t.databaseName); + + const RFC = 'TOAH680201RA2'; + const FI = '2025-08-01'; + const FF = '2025-08-31'; + const TARGET_UUID = '8ec2eaf3-7879-11f0-81a8-8daae9822b10'; + + // ─────────────────────────────────────────────────────────────────────────── + // 0) Datos del CFDI target + // ─────────────────────────────────────────────────────────────────────────── + const { rows: [cfdi] } = await pool.query(` + SELECT uuid, tipo_comprobante, metodo_pago, forma_pago, uso_cfdi, + total_mxn, monto_pago_mxn, iva_traslado_pago_mxn, ieps_traslado_pago_mxn, + uuid_relacionado, regimen_fiscal_receptor, fecha_pago_p + FROM cfdis WHERE uuid = $1 + `, [TARGET_UUID]); + + console.log('═══ CFDI target ═══'); + console.log(` UUID: ${cfdi.uuid}`); + console.log(` Tipo: ${cfdi.tipo_comprobante} (${cfdi.uso_cfdi || '?'})`); + console.log(` monto_pago_mxn: $${cfdi.monto_pago_mxn}`); + console.log(` iva_traslado_pago: $${cfdi.iva_traslado_pago_mxn ?? 'null'}`); + console.log(` ieps_traslado_pago: $${cfdi.ieps_traslado_pago_mxn ?? 'null'}`); + console.log(` forma_pago: ${cfdi.forma_pago ?? 'NULL'}`); + console.log(` uuid_relacionado: ${cfdi.uuid_relacionado}`); + console.log(` fecha_pago_p: ${cfdi.fecha_pago_p}`); + + // Net pagado según fórmula de deducciones (P) + const monto = Number(cfdi.monto_pago_mxn || 0); + const ivaPago = Number(cfdi.iva_traslado_pago_mxn || 0); + const iepsPago = Number(cfdi.ieps_traslado_pago_mxn || 0); + const ivaClamped = Math.min(ivaPago, monto * 0.16); + const netoP = monto - ivaClamped - iepsPago; + console.log(`\n → Aporte neto a deducciones (formula P): $${netoP.toFixed(2)}`); + console.log(` monto - LEAST(iva, monto*0.16) - ieps = ${monto} - ${ivaClamped.toFixed(2)} - ${iepsPago}`); + + // CFDI relacionado + const { rows: [rel] } = await pool.query(` + SELECT uuid, tipo_comprobante, metodo_pago, uso_cfdi, total_mxn, cfdi_tipo_relacion + FROM cfdis WHERE LOWER(uuid) = LOWER($1) + `, [cfdi.uuid_relacionado]); + if (rel) { + console.log(`\n uuid_relacionado apunta a:`); + console.log(` ${rel.uuid} | ${rel.tipo_comprobante} ${rel.metodo_pago} | uso_cfdi=${rel.uso_cfdi} | total=$${rel.total_mxn}`); + console.log(` cfdi_tipo_relacion: ${rel.cfdi_tipo_relacion ?? 'null'}`); + console.log(` ¿es activo? uso_cfdi=${rel.uso_cfdi} → ${['I01','I02','I03','I04','I05','I06','I07','I08'].includes(rel.uso_cfdi) ? '🔴 SÍ' : '🟢 NO'}`); + } + + // ─────────────────────────────────────────────────────────────────────────── + // 1) Predicado de filtros + // ─────────────────────────────────────────────────────────────────────────── + console.log('\n═══ Evaluación de predicados sobre el CFDI target ═══'); + + const ACTIVOS = "('I01','I02','I03','I04','I05','I06','I07','I08')"; + const t1 = await pool.query(` + SELECT + (COALESCE(forma_pago, '') = '01' AND COALESCE(monto_pago_mxn, 0) > 2000) AS no_deducible_efectivo, + (tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS} + )) AS p_paga_activo + FROM cfdis WHERE uuid = $1 + `, [TARGET_UUID]); + console.log(` no_deducible_efectivo (forma_pago=01 AND >2k): ${t1.rows[0].no_deducible_efectivo ? '🔴 TRUE' : '🟢 FALSE'}`); + console.log(` p_paga_activo (regla activos): ${t1.rows[0].p_paga_activo ? '🔴 TRUE' : '🟢 FALSE'}`); + + // ─────────────────────────────────────────────────────────────────────────── + // 2) Total de deducciones de Husberto en agosto, con/sin filtro de activos + // ─────────────────────────────────────────────────────────────────────────── + console.log('\n═══ Suma TOTAL de deducciones (régimen 612 — Husberto, agosto 2025) ═══'); + + const sumar = async (extraSQL: string) => { + // I PUE + const { rows: [iPUE] } = await pool.query(` + SELECT COALESCE(SUM(COALESCE(total_mxn,0) - COALESCE(iva_traslado_mxn,0) - COALESCE(ieps_traslado_mxn,0) - COALESCE(impuestos_locales_trasladado_mxn,0)),0)::numeric(14,2) as monto + FROM cfdis + WHERE UPPER(rfc_receptor) = $1 AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $2::date AND fecha_emision < ($3::date + interval '1 day') + AND NOT (COALESCE(forma_pago,'') = '01' AND COALESCE(total_mxn,0) > 2000) + ${extraSQL} + `, [RFC, FI, FF]); + // P + const { rows: [pCfdis] } = await pool.query(` + SELECT COALESCE(SUM(COALESCE(monto_pago_mxn,0) - LEAST(COALESCE(iva_traslado_pago_mxn,0), COALESCE(monto_pago_mxn,0)*0.16) - COALESCE(ieps_traslado_pago_mxn,0)),0)::numeric(14,2) as monto + FROM cfdis + WHERE UPPER(rfc_receptor) = $1 AND tipo_comprobante = 'P' + AND status NOT IN ('Cancelado','0') + AND fecha_pago_p >= $2::date AND fecha_pago_p < ($3::date + interval '1 day') + AND NOT (COALESCE(forma_pago,'') = '01' AND COALESCE(monto_pago_mxn,0) > 2000) + ${extraSQL} + `, [RFC, FI, FF]); + return { iPUE: Number(iPUE.monto), p: Number(pCfdis.monto) }; + }; + + const ACTIVOS_FILTER = ` + AND NOT (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS}) + AND NOT (tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS} + )) + AND NOT (tipo_comprobante = 'E' AND cfdis.cfdis_relacionados IS NOT NULL AND EXISTS ( + SELECT 1 FROM cfdis r_act + WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|')) + AND ( + (r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS}) + OR (r_act.tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis pi_act + WHERE LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado) + AND pi_act.tipo_comprobante = 'I' + AND pi_act.uso_cfdi IN ${ACTIVOS} + )) + ) + )) + `; + + const ON = await sumar(''); + const OFF = await sumar(ACTIVOS_FILTER); + + console.log(`\n┌──────────────────┬────────────────┬────────────────┬────────────────┐`); + console.log(`│ Categoría │ Activos ON │ Activos OFF │ Diferencia │`); + console.log(`├──────────────────┼────────────────┼────────────────┼────────────────┤`); + console.log(`│ I PUE recibidas │ $${String(ON.iPUE.toFixed(2)).padStart(13)} │ $${String(OFF.iPUE.toFixed(2)).padStart(13)} │ $${String((ON.iPUE-OFF.iPUE).toFixed(2)).padStart(13)} │`); + console.log(`│ P recibidos │ $${String(ON.p.toFixed(2)).padStart(13)} │ $${String(OFF.p.toFixed(2)).padStart(13)} │ $${String((ON.p-OFF.p).toFixed(2)).padStart(13)} │`); + console.log(`├──────────────────┼────────────────┼────────────────┼────────────────┤`); + const totON = ON.iPUE + ON.p; + const totOFF = OFF.iPUE + OFF.p; + console.log(`│ TOTAL deducción │ $${String(totON.toFixed(2)).padStart(13)} │ $${String(totOFF.toFixed(2)).padStart(13)} │ $${String((totON-totOFF).toFixed(2)).padStart(13)} │`); + console.log(`└──────────────────┴────────────────┴────────────────┴────────────────┘`); + + console.log(`\n→ El CFDI ${TARGET_UUID.slice(0,8)} aporta $${netoP.toFixed(2)} a "P recibidos" cuando ON, $0 cuando OFF`); + console.log(` (Su exclusión por activos representa el ${((netoP / (ON.p - OFF.p)) * 100).toFixed(0)}% de la diferencia en P recibidos)`); + + await prisma.$disconnect(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/debug-deducciones-husberto.ts b/apps/api/scripts/debug-deducciones-husberto.ts new file mode 100644 index 0000000..1cfa0a5 --- /dev/null +++ b/apps/api/scripts/debug-deducciones-husberto.ts @@ -0,0 +1,111 @@ +/** + * Reproduce el cálculo de deducciones para Husberto en agosto 2025 con + * considerarActivos=true vs false, y muestra la diferencia esperada. + * Apunta directo al SQL para descartar bugs de wire/cache/UI. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const t = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' } }); + if (!t) { console.log('Patito tenant not found'); return; } + const pool = await tenantDb.getPool(t.id, t.databaseName); + + const RFC = 'TOAH680201RA2'; + const FI = '2025-08-01'; + const FF = '2025-08-31'; + + console.log(`Husberto (${RFC}), agosto 2025\n`); + + // 0) Lista TODOS los P recibidos en el período (sin filtros) + const all = await pool.query(` + SELECT uuid, monto_pago_mxn, forma_pago, fecha_pago_p, uuid_relacionado + FROM cfdis + WHERE UPPER(rfc_receptor) = $1 + AND tipo_comprobante = 'P' + AND status NOT IN ('Cancelado','0') + AND fecha_pago_p >= $2::date AND fecha_pago_p < ($3::date + interval '1 day') + ORDER BY monto_pago_mxn DESC + `, [RFC, FI, FF]); + console.log(`Total P recibidos en agosto 2025 (sin filtros): ${all.rows.length}`); + for (const r of all.rows) { + console.log(` ${r.uuid} | $${r.monto_pago_mxn} | forma_pago=${r.forma_pago} | uuid_rel=${r.uuid_relacionado}`); + } + console.log(); + + // 1) Suma de P recibidos sin filtro extra + const sinFiltro = await pool.query(` + SELECT COUNT(*)::int as n, + COALESCE(SUM(COALESCE(monto_pago_mxn,0)),0)::numeric(14,2) as bruto, + COALESCE(SUM(COALESCE(monto_pago_mxn,0) - LEAST(COALESCE(iva_traslado_pago_mxn,0), COALESCE(monto_pago_mxn,0)*0.16) - COALESCE(ieps_traslado_pago_mxn,0)),0)::numeric(14,2) as neto + FROM cfdis + WHERE UPPER(rfc_receptor) = $1 + AND tipo_comprobante = 'P' + AND status NOT IN ('Cancelado','0') + AND fecha_pago_p >= $2::date AND fecha_pago_p < ($3::date + interval '1 day') + AND NOT (COALESCE(forma_pago, '') = '01' AND COALESCE(monto_pago_mxn, 0) > 2000) + `, [RFC, FI, FF]); + console.log(`P recibidos SIN filtro activos (CON filtro no-deducible): n=${sinFiltro.rows[0].n}, bruto=$${sinFiltro.rows[0].bruto}, neto=$${sinFiltro.rows[0].neto}`); + + // 2) Misma query CON el filtro de activos (regla 2: P paga I de activo) + const ACTIVOS = "('I01','I02','I03','I04','I05','I06','I07','I08')"; + const conFiltro = await pool.query(` + SELECT COUNT(*)::int as n, + COALESCE(SUM(COALESCE(monto_pago_mxn,0)),0)::numeric(14,2) as bruto, + COALESCE(SUM(COALESCE(monto_pago_mxn,0) - LEAST(COALESCE(iva_traslado_pago_mxn,0), COALESCE(monto_pago_mxn,0)*0.16) - COALESCE(ieps_traslado_pago_mxn,0)),0)::numeric(14,2) as neto + FROM cfdis + WHERE UPPER(rfc_receptor) = $1 + AND tipo_comprobante = 'P' + AND status NOT IN ('Cancelado','0') + AND fecha_pago_p >= $2::date AND fecha_pago_p < ($3::date + interval '1 day') + AND NOT (COALESCE(forma_pago, '') = '01' AND COALESCE(monto_pago_mxn, 0) > 2000) + AND NOT (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS}) + AND NOT (tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS} + )) + AND NOT (tipo_comprobante = 'E' AND cfdis.cfdis_relacionados IS NOT NULL AND EXISTS ( + SELECT 1 FROM cfdis r_act + WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|')) + AND ( + (r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS}) + OR (r_act.tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis pi_act + WHERE LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado) + AND pi_act.tipo_comprobante = 'I' + AND pi_act.uso_cfdi IN ${ACTIVOS} + )) + ) + )) + `, [RFC, FI, FF]); + console.log(`P recibidos CON filtro activos: n=${conFiltro.rows[0].n}, bruto=$${conFiltro.rows[0].bruto}, neto=$${conFiltro.rows[0].neto}`); + + console.log(`\n→ Diferencia esperada al desactivar Considerar Activos:`); + console.log(` Bruto: $${(Number(sinFiltro.rows[0].bruto) - Number(conFiltro.rows[0].bruto)).toLocaleString('es-MX')}`); + console.log(` Neto: $${(Number(sinFiltro.rows[0].neto) - Number(conFiltro.rows[0].neto)).toLocaleString('es-MX')}`); + + // 3) Lista los P específicos que se filtran + console.log(`\nDetalle de P que SE FILTRAN al desactivar activos:`); + const filtrados = await pool.query(` + SELECT uuid, monto_pago_mxn, iva_traslado_pago_mxn, uuid_relacionado, fecha_pago_p + FROM cfdis + WHERE UPPER(rfc_receptor) = $1 + AND tipo_comprobante = 'P' + AND status NOT IN ('Cancelado','0') + AND fecha_pago_p >= $2::date AND fecha_pago_p < ($3::date + interval '1 day') + AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS} + ) + `, [RFC, FI, FF]); + for (const r of filtrados.rows) { + console.log(` ${r.uuid} | $${r.monto_pago_mxn} → uuid_rel: ${r.uuid_relacionado}`); + } + + await prisma.$disconnect(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/debug-drill-buckets.ts b/apps/api/scripts/debug-drill-buckets.ts new file mode 100644 index 0000000..ba3c823 --- /dev/null +++ b/apps/api/scripts/debug-drill-buckets.ts @@ -0,0 +1,71 @@ +/** + * Ejecuta los 3 nuevos buckets de drill-down (ncs_emitidas, ncs_recibidas, + * no_deducibles_efectivo) directamente contra una BD tenant para verificar + * que cada uno produce resultados distintos. Sirve para descartar hipótesis + * de bug en frontend / cache / dev server stale. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const rfc = process.argv[2] || 'DESPACHO_MO7JE8BZ_VDOPR'; + const fi = process.argv[3] || '2025-08-01'; + const ff = process.argv[4] || '2025-08-31'; + + const t = await prisma.tenant.findFirst({ where: { rfc } }); + if (!t) { console.log('Tenant', rfc, 'no encontrado'); return; } + const pool = await tenantDb.getPool(t.id, t.databaseName); + + console.log(`Tenant: ${rfc} — Período: ${fi} → ${ff}\n`); + + const buckets = [ + { + name: 'ncs_emitidas', + sql: ` + SELECT COUNT(*)::int as n, COALESCE(SUM(total_mxn),0)::numeric(14,2) as total + FROM cfdis + WHERE type = 'EMITIDO' + AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + AND regimen_fiscal_emisor IS NOT NULL + `, + }, + { + name: 'ncs_recibidas', + sql: ` + SELECT COUNT(*)::int as n, COALESCE(SUM(total_mxn),0)::numeric(14,2) as total + FROM cfdis + WHERE type = 'RECIBIDO' + AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + AND regimen_fiscal_receptor IS NOT NULL + `, + }, + { + name: 'no_deducibles_efectivo', + sql: ` + SELECT COUNT(*)::int as n, COALESCE(SUM(total_mxn),0)::numeric(14,2) as total + FROM cfdis + WHERE type = 'RECIBIDO' + AND forma_pago = '01' + AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE' AND COALESCE(total_mxn, 0) > 2000) + OR (tipo_comprobante = 'P' AND COALESCE(monto_pago_mxn, 0) > 2000) + ) + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + AND regimen_fiscal_receptor IS NOT NULL + `, + }, + ]; + + for (const b of buckets) { + const { rows: [r] } = await pool.query(b.sql, [fi, ff]); + console.log(`${b.name.padEnd(28)} → ${r.n} fila(s), total = $${Number(r.total).toLocaleString('es-MX')}`); + } + + await prisma.$disconnect(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/debug-i07-ppd.ts b/apps/api/scripts/debug-i07-ppd.ts new file mode 100644 index 0000000..c0fe7fd --- /dev/null +++ b/apps/api/scripts/debug-i07-ppd.ts @@ -0,0 +1,169 @@ +/** + * Diseca cómo se compensa la I PPD con cfdi_tipo_relacion='07' (aplicación + * de anticipo) en el cálculo de deducciones, evaluando: + * - Si NO entra al sumatorio normal (I PUE / P) por ser PPD + * - Si entra a la compensación I/07 PPD ↔ E del mismo mes + * - Si tiene cfdis_relacionados (qué referencia hacia atrás) + * - Si es referenciada por algún CFDI hacia adelante (P, E, otra I) + * - Cómo afecta con considerarActivos ON vs OFF + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const t = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' } }); + if (!t) { console.log('Patito tenant not found'); return; } + const pool = await tenantDb.getPool(t.id, t.databaseName); + + const TARGET = '5c874749-748f-11f0-96b1-2b9310891836'; + const RFC = 'TOAH680201RA2'; + + // ─────────────────────────────────────────────────────────────────────────── + // 0) Datos del CFDI + // ─────────────────────────────────────────────────────────────────────────── + const { rows: [c] } = await pool.query(` + SELECT uuid, type, tipo_comprobante, metodo_pago, forma_pago, uso_cfdi, + cfdi_tipo_relacion, cfdis_relacionados, + total_mxn, iva_traslado_mxn, ieps_traslado_mxn, + rfc_emisor, nombre_emisor, regimen_fiscal_emisor, + rfc_receptor, nombre_receptor, regimen_fiscal_receptor, + fecha_emision, fecha_pago_p, status, + saldo_pendiente_mxn + FROM cfdis WHERE LOWER(uuid) = LOWER($1) + `, [TARGET]); + + console.log('═══ CFDI ═══'); + for (const [k, v] of Object.entries(c)) { + console.log(` ${k.padEnd(28)} ${v}`); + } + + // ─────────────────────────────────────────────────────────────────────────── + // 1) ¿Hace referencia hacia atrás (vía cfdis_relacionados)? + // ─────────────────────────────────────────────────────────────────────────── + console.log('\n═══ Referencias hacia atrás (cfdis_relacionados) ═══'); + if (!c.cfdis_relacionados) { + console.log(' (ninguna — cfdis_relacionados es NULL)'); + } else { + const uuids = String(c.cfdis_relacionados).split('|').map((s: string) => s.trim()).filter(Boolean); + for (const u of uuids) { + const { rows: [rel] } = await pool.query(` + SELECT uuid, tipo_comprobante, metodo_pago, total_mxn, fecha_emision, cfdi_tipo_relacion + FROM cfdis WHERE LOWER(uuid) = LOWER($1) + `, [u]); + console.log(` ${u} →`, rel ?? '(no encontrado)'); + } + } + + // ─────────────────────────────────────────────────────────────────────────── + // 2) ¿Es referenciada hacia adelante? (P que la pague, E que la cancele, otra I tipo_relacion=07 que sustituya) + // ─────────────────────────────────────────────────────────────────────────── + console.log('\n═══ CFDIs que referencian a este (hacia adelante) ═══'); + + // P que la pagan vía uuid_relacionado + const { rows: pagos } = await pool.query(` + SELECT uuid, monto_pago_mxn, fecha_pago_p + FROM cfdis + WHERE tipo_comprobante = 'P' + AND LOWER(uuid_relacionado) = LOWER($1) + AND status NOT IN ('Cancelado','0') + ORDER BY fecha_pago_p + `, [TARGET]); + console.log(` P que la pagan (${pagos.length}):`); + let totalPagado = 0; + for (const p of pagos) { + totalPagado += Number(p.monto_pago_mxn || 0); + console.log(` ${p.uuid} | $${p.monto_pago_mxn} | ${p.fecha_pago_p}`); + } + console.log(` → Total pagado vía P: $${totalPagado.toLocaleString('es-MX')}`); + console.log(` Total CFDI original: $${c.total_mxn}`); + console.log(` Saldo pendiente: $${c.saldo_pendiente_mxn ?? '?'}`); + + // E que la cancelan vía cfdis_relacionados + const { rows: ecanc } = await pool.query(` + SELECT uuid, tipo_comprobante, metodo_pago, total_mxn, cfdi_tipo_relacion, fecha_emision + FROM cfdis + WHERE tipo_comprobante = 'E' + AND cfdis_relacionados IS NOT NULL + AND LOWER($1) = ANY(string_to_array(LOWER(cfdis_relacionados), '|')) + AND status NOT IN ('Cancelado','0') + `, [TARGET]); + console.log(`\n E que la referencian (${ecanc.length}):`); + for (const e of ecanc) { + console.log(` ${e.uuid} | total=$${e.total_mxn} | tipo_rel=${e.cfdi_tipo_relacion} | ${e.fecha_emision}`); + } + + // ─────────────────────────────────────────────────────────────────────────── + // 3) Compensación I/07 PPD ↔ E lado RECEPTOR (mismo mes) + // ─────────────────────────────────────────────────────────────────────────── + console.log('\n═══ ¿Entra en compensación I/07 PPD ↔ E (mes/año del CFDI)? ═══'); + + const fecha = new Date(c.fecha_emision); + const mesAnio = `${fecha.getFullYear()}-${String(fecha.getMonth() + 1).padStart(2, '0')}`; + console.log(` CFDI mes/año: ${mesAnio}`); + console.log(` cfdi_tipo_relacion='07': ${c.cfdi_tipo_relacion === '07' ? '✓ SÍ' : '✗ NO'}`); + console.log(` metodo_pago='PPD': ${c.metodo_pago === 'PPD' ? '✓ SÍ' : '✗ NO'}`); + + if (c.cfdi_tipo_relacion === '07' && c.metodo_pago === 'PPD') { + // Calcular el aporte que tendría a la compensación (suma de E del mismo mes) + const { rows: comp } = await pool.query(` + SELECT + COALESCE(SUM( + COALESCE(e.total_mxn, 0) + - COALESCE(e.iva_traslado_mxn, 0) + - COALESCE(e.ieps_traslado_mxn, 0) + - COALESCE(e.impuestos_locales_trasladado_mxn, 0) + ), 0)::numeric(14,2) AS aporte + FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND e.metodo_pago = 'PUE' + AND e.status NOT IN ('Cancelado','0') + AND UPPER(e.rfc_receptor) = $1 + AND LOWER($2) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND date_trunc('month', e.fecha_emision) = date_trunc('month', $3::timestamp) + `, [RFC, TARGET, c.fecha_emision]); + console.log(`\n Aporte a la compensación (suma E mismo mes): $${comp[0].aporte}`); + if (Number(comp[0].aporte) > 0) { + console.log(` → SÍ entra en compensación`); + } else { + console.log(` → NO entra (no hay E en mismo mes que la referencien)`); + } + } + + // ─────────────────────────────────────────────────────────────────────────── + // 4) ¿Aparece en el cálculo "I PUE recibidas" o "P recibidos"? + // ─────────────────────────────────────────────────────────────────────────── + console.log('\n═══ ¿Aparece en el cálculo directo? ═══'); + console.log(` I PUE recibidas requiere: tipo_comprobante='I' AND metodo_pago='PUE'`); + console.log(` Este CFDI: tipo_comprobante='${c.tipo_comprobante}', metodo_pago='${c.metodo_pago}'`); + console.log(` → ${c.tipo_comprobante === 'I' && c.metodo_pago === 'PUE' ? '✓ SÍ entra' : '✗ NO entra'} (es ${c.tipo_comprobante} ${c.metodo_pago})`); + + // ─────────────────────────────────────────────────────────────────────────── + // 5) Predicado de filtro de activos + // ─────────────────────────────────────────────────────────────────────────── + console.log('\n═══ Predicado de filtro de activos sobre este CFDI ═══'); + const ACTIVOS = "('I01','I02','I03','I04','I05','I06','I07','I08')"; + const t1 = await pool.query(` + SELECT + (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS}) AS regla1_directo, + (tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS} + )) AS regla2, + (tipo_comprobante = 'E' AND cfdis.cfdis_relacionados IS NOT NULL AND EXISTS ( + SELECT 1 FROM cfdis r_act + WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|')) + AND (r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS}) + )) AS regla3 + FROM cfdis WHERE LOWER(uuid) = LOWER($1) + `, [TARGET]); + console.log(` regla1 (I directo activo): ${t1.rows[0].regla1_directo ? '🔴 TRUE' : '🟢 FALSE'}`); + console.log(` regla2 (P paga I activo): ${t1.rows[0].regla2 ? '🔴 TRUE' : '🟢 FALSE'}`); + console.log(` regla3 (E ref. I/P activo): ${t1.rows[0].regla3 ? '🔴 TRUE' : '🟢 FALSE'}`); + const filtrado = t1.rows[0].regla1_directo || t1.rows[0].regla2 || t1.rows[0].regla3; + console.log(` → Si "Considerar activos" OFF → ${filtrado ? '🔴 EXCLUIDO' : '🟢 PASA'}`); + + await prisma.$disconnect(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/debug-i07.ts b/apps/api/scripts/debug-i07.ts new file mode 100644 index 0000000..30145df --- /dev/null +++ b/apps/api/scripts/debug-i07.ts @@ -0,0 +1,88 @@ +/** + * Desglosa cada I/07 recibida de un contribuyente en un rango, mostrando: + * - NETO_CUSTOM(I/07) + * - UUIDs en cfdis_relacionados + * - NETO_CUSTOM de cada relacionada vigente + * - Contribución neta de la I/07 al gasto + * + * Útil para detectar: + * - Múltiples I/07 que referencian el mismo anticipo (doble-resta) + * - Anticipos fuera del periodo que dominan la compensación + * - UUIDs relacionados incorrectos (apuntan a CFDIs enormes no-anticipo) + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'd745a915-6a23-4818-944b-a7e1e18e536a'; +const yearMonth = process.argv[4] || '2025-07'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const [anio, mes] = yearMonth.split('-').map(Number); + const lastDay = new Date(anio, mes, 0).getDate(); + const fi = `${yearMonth}-01`; + const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`; + + const NETO = (a: string) => `( + COALESCE(${a}.total_mxn,0) - COALESCE(${a}.iva_traslado_mxn,0) + COALESCE(${a}.iva_retencion_mxn,0) + + COALESCE(${a}.isr_retencion_mxn,0) + - COALESCE(${a}.ieps_traslado_mxn,0) + COALESCE(${a}.ieps_retencion_mxn,0) + - COALESCE(${a}.impuestos_locales_trasladado_mxn,0) + COALESCE(${a}.impuestos_locales_retenidos_mxn,0) + )`; + + const { rows } = await pool.query( + `SELECT c.uuid, c.fecha_emision, c.total_mxn, c.rfc_emisor, c.cfdis_relacionados, + ${NETO('c')} AS neto_i07 + FROM cfdis c + WHERE c.type='RECIBIDO' AND c.tipo_comprobante='I' AND c.metodo_pago='PUE' + AND c.cfdi_tipo_relacion='07' + AND c.status NOT IN ('Cancelado','0') + AND c.fecha_emision >= $1::date AND c.fecha_emision < ($2::date + interval '1 day') + AND c.contribuyente_id = $3 + ORDER BY c.fecha_emision`, + [fi, ff, contribuyenteId], + ); + + console.log(`\n=== I/07 RECIBIDAS en ${fi} a ${ff} ===`); + console.log(`Total I/07: ${rows.length}`); + + let sumContrib = 0; + for (const r of rows) { + const relsUuids = (r.cfdis_relacionados || '').split('|').filter(Boolean).map((u: string) => u.toLowerCase()); + console.log(`\n I/07 ${r.uuid.substring(0,8)} — fecha=${r.fecha_emision.toISOString().slice(0,10)} — emisor=${r.rfc_emisor}`); + console.log(` total_mxn: ${Number(r.total_mxn).toFixed(2)}`); + console.log(` NETO(I/07): ${Number(r.neto_i07).toFixed(2)}`); + console.log(` relacionados (${relsUuids.length}):`); + + let sumRel = 0; + if (relsUuids.length > 0) { + const { rows: rels } = await pool.query( + `SELECT uuid, fecha_emision, total_mxn, tipo_comprobante, metodo_pago, status, ${NETO('a')} AS neto_rel + FROM cfdis a + WHERE LOWER(a.uuid) = ANY($1::text[])`, + [relsUuids], + ); + for (const rel of rels) { + const vig = rel.status === 'Vigente' ? '✓' : '✗'; + console.log(` ${vig} ${rel.uuid.substring(0,8)} ${rel.tipo_comprobante} ${rel.metodo_pago || '-'} fecha=${rel.fecha_emision?.toISOString?.().slice(0,10) || '-'} total=${Number(rel.total_mxn).toFixed(2)} NETO=${Number(rel.neto_rel).toFixed(2)}`); + if (rel.status === 'Vigente') sumRel += Number(rel.neto_rel); + } + const missing = relsUuids.filter((u: string) => !rels.find((x: any) => x.uuid.toLowerCase() === u)); + if (missing.length > 0) { + console.log(` ⚠️ ${missing.length} UUID(s) relacionados NO están en BD:`); + for (const m of missing) console.log(` ${m}`); + } + } + const contrib = Number(r.neto_i07) - sumRel; + sumContrib += contrib; + console.log(` Σ NETO(rel vigentes): ${sumRel.toFixed(2)}`); + console.log(` CONTRIB: ${contrib.toFixed(2)} ${contrib < 0 ? '⚠️ NEGATIVA' : ''}`); + } + + console.log(`\nSuma total contribuciones I/07: ${sumContrib.toFixed(2)}`); + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/debug-ingresos-horux-may-wider.ts b/apps/api/scripts/debug-ingresos-horux-may-wider.ts new file mode 100644 index 0000000..30ee0a2 --- /dev/null +++ b/apps/api/scripts/debug-ingresos-horux-may-wider.ts @@ -0,0 +1,104 @@ +/** + * Amplía la inspección: lista TODOS los CFDIs de mayo-2025 donde Horux 360 + * aparece como emisor o receptor, marcando cuáles entran al bucket ingresos + * y cuáles no + por qué. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { resolveContribuyenteContext } from '../src/utils/contribuyente-context.js'; + +const TENANT_RFC = 'DESPACHO_MO3NI6U8_B9VGG'; +const CONTRIB_ID = 'b3761db6-0b8d-4251-8078-4ddc31e9c75b'; +const FI = '2025-05-01'; +const FF = '2025-05-31'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: TENANT_RFC }, select: { id: true, databaseName: true }, + }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + const ctx = await resolveContribuyenteContext(pool, tenant.id, CONTRIB_ID); + + console.log(`\n=== TODOS los CFDIs de Horux 360 en mayo-2025 (como emisor o receptor) ===\n`); + + const { rows } = await pool.query( + `SELECT uuid, type, tipo_comprobante, metodo_pago, status, + regimen_fiscal_emisor, regimen_fiscal_receptor, + rfc_emisor, rfc_receptor, nombre_receptor, nombre_emisor, + total_mxn, monto_pago_mxn, cfdi_tipo_relacion, fecha_emision, source + FROM cfdis + WHERE ((${ctx.esEmisor}) OR (${ctx.esReceptor})) + AND fecha_emision >= $1::date + AND fecha_emision < ($2::date + interval '1 day') + ORDER BY fecha_emision, tipo_comprobante, total_mxn DESC`, + [FI, FF], + ); + + console.log(`Total CFDIs encontrados: ${rows.length}\n`); + + const buckets: Record = { + ingresosG1: [], + ingresosG3: [], + ingresosSueldos: [], + noIncluye_canceladoOinvalido: [], + noIncluye_regimenFuera: [], + noIncluye_comoReceptor: [], + noIncluye_otroMotivo: [], + }; + + const G1 = ['606', '612', '621', '625', '626']; + const G3 = ['601', '603', '607', '608', '610', '611', '614', '615', '620', '622', '623', '624']; + + for (const r of rows) { + const cancel = ['Cancelado', '0'].includes(r.status); + const esEmisorRow = String(r.rfc_emisor).toUpperCase() === 'HTS240708LJA'; + const regE = r.regimen_fiscal_emisor; + const regR = r.regimen_fiscal_receptor; + + if (cancel) { buckets.noIncluye_canceladoOinvalido.push(r); continue; } + + if (esEmisorRow) { + if (G1.includes(regE)) { + if ((r.tipo_comprobante === 'I' && r.metodo_pago === 'PUE') || + (r.tipo_comprobante === 'P') || + (r.tipo_comprobante === 'E' && r.metodo_pago === 'PUE')) { + buckets.ingresosG1.push(r); continue; + } + } + if (G3.includes(regE)) { + if ((r.tipo_comprobante === 'I' && ['PUE', 'PPD'].includes(r.metodo_pago)) || + (r.tipo_comprobante === 'E' && r.metodo_pago === 'PUE')) { + buckets.ingresosG3.push(r); continue; + } + } + if (!G1.includes(regE) && !G3.includes(regE)) { + buckets.noIncluye_regimenFuera.push({ ...r, reason: `emisor régimen ${regE} fuera de grupo` }); + continue; + } + buckets.noIncluye_otroMotivo.push({ ...r, reason: `emisor tipo=${r.tipo_comprobante}/${r.metodo_pago} no matchea` }); + continue; + } + + // No emisor → receptor + if (r.tipo_comprobante === 'N' && r.metodo_pago === 'PUE' && regR === '605') { + buckets.ingresosSueldos.push(r); continue; + } + buckets.noIncluye_comoReceptor.push({ ...r, reason: 'es receptor, no cuenta como ingreso (salvo N/605)' }); + } + + const fmt = (n: any) => Number(n || 0).toFixed(2); + + for (const [name, list] of Object.entries(buckets)) { + if (list.length === 0) continue; + console.log(`\n--- ${name} (${list.length}) ---`); + for (const r of list) { + const fe = r.fecha_emision?.toISOString?.()?.slice(0, 10) || r.fecha_emision; + const reason = r.reason ? ` | ${r.reason}` : ''; + console.log(` ${fe} ${r.tipo_comprobante}/${r.metodo_pago || '-'} status=${r.status} regE=${r.regimen_fiscal_emisor} regR=${r.regimen_fiscal_receptor} ${r.rfc_emisor}→${r.rfc_receptor} total=${fmt(r.total_mxn)} mp=${fmt(r.monto_pago_mxn)} ${r.uuid.substring(0,8)}${reason}`); + } + } + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/debug-ingresos-horux-may.ts b/apps/api/scripts/debug-ingresos-horux-may.ts new file mode 100644 index 0000000..927c43a --- /dev/null +++ b/apps/api/scripts/debug-ingresos-horux-may.ts @@ -0,0 +1,111 @@ +/** + * Debug ingresos Horux 360 mayo-2025 post-Método A: + * - Llama al KPI (calcularIngresosPorRegimen) + * - Lista los CFDIs que entran al drill-down (mismos filtros del controller) + * - Suma manualmente para ver dónde está la discrepancia + */ +process.env.METRICAS_BYPASS_CACHE = '1'; +import { prisma, tenantDb } from '../src/config/database.js'; +import { calcularIngresosPorRegimen, GRUPO_PF_EMPRESARIAL, GRUPO_PM_OTROS } from '../src/services/dashboard.service.js'; +import { resolveContribuyenteContext } from '../src/utils/contribuyente-context.js'; + +const TENANT_RFC = 'DESPACHO_MO3NI6U8_B9VGG'; +const CONTRIB_ID = 'b3761db6-0b8d-4251-8078-4ddc31e9c75b'; // Horux 360 +const FI = '2025-05-01'; +const FF = '2025-05-31'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: TENANT_RFC }, + select: { id: true, databaseName: true }, + }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + const ctx = await resolveContribuyenteContext(pool, tenant.id, CONTRIB_ID); + + console.log(`\n=== KPI calcularIngresosPorRegimen ===`); + const kpi = await calcularIngresosPorRegimen( + pool, tenant.id, FI, FF, undefined, undefined, false, CONTRIB_ID, + ); + console.log(`Total KPI: ${kpi.total.toFixed(2)}`); + for (const r of kpi.porRegimen) { + console.log(` ${r.regimenClave} ${r.regimenDescripcion.substring(0, 40).padEnd(40)} ${r.monto.toFixed(2)}`); + } + + // Replica de los filtros del drill-down bucket 'ingresos' (cfdi.controller.ts:163-187) + const IMP_TRAS = `COALESCE(iva_traslado_mxn,0) + COALESCE(ieps_traslado_mxn,0) + COALESCE(impuestos_locales_trasladado_mxn,0)`; + const IMP_TRAS_PAGO = `COALESCE(iva_traslado_pago_mxn,0) + COALESCE(ieps_traslado_pago_mxn,0)`; + const VIGENTE = `status NOT IN ('Cancelado', '0')`; + const CLAVES = `('84121603','93161608','85101501','85121800')`; + const EXCL_MONTO = `COALESCE((SELECT SUM(COALESCE(cc.importe_mxn,0)-COALESCE(cc.descuento_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ${CLAVES}),0)`; + + const g1 = GRUPO_PF_EMPRESARIAL.map(r => `'${r}'`).join(','); + const g3 = GRUPO_PM_OTROS.map(r => `'${r}'`).join(','); + + const drillSql = ` + SELECT id, uuid, type, tipo_comprobante, metodo_pago, regimen_fiscal_emisor, + regimen_fiscal_receptor, rfc_emisor, rfc_receptor, nombre_receptor, + total_mxn, iva_traslado_mxn, ieps_traslado_mxn, impuestos_locales_trasladado_mxn, + monto_pago_mxn, iva_traslado_pago_mxn, ieps_traslado_pago_mxn, + cfdi_tipo_relacion, fecha_emision, fecha_pago_p, source, + -- neto (lo que "contribuye" a ingresos según grupo) + CASE + WHEN tipo_comprobante='I' THEN (COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})) + WHEN tipo_comprobante='E' THEN -(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})) + WHEN tipo_comprobante='P' THEN (COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO})) + WHEN tipo_comprobante='N' THEN COALESCE(total_mxn,0) + ELSE 0 + END AS aporte + FROM cfdis + WHERE ${VIGENTE} + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + AND ( + (${ctx.esEmisor} AND regimen_fiscal_emisor IN (${g1}) AND ( + (tipo_comprobante='I' AND metodo_pago='PUE') + OR (tipo_comprobante='P') + OR (tipo_comprobante='E' AND metodo_pago='PUE') + )) + OR (${ctx.esReceptor} AND tipo_comprobante='N' AND metodo_pago='PUE' AND regimen_fiscal_receptor='605') + OR (${ctx.esEmisor} AND regimen_fiscal_emisor IN (${g3}) AND ( + (tipo_comprobante='I' AND metodo_pago IN ('PUE','PPD')) + OR (tipo_comprobante='E' AND metodo_pago='PUE') + )) + ) + ORDER BY fecha_emision, tipo_comprobante, total_mxn DESC + `; + + const { rows } = await pool.query(drillSql, [FI, FF]); + console.log(`\n=== Drill-down (${rows.length} CFDIs) ===`); + + let sumDrill = 0; + const perRegimen: Record = {}; + + for (const r of rows) { + const aporte = Number(r.aporte || 0); + sumDrill += aporte; + const reg = r.regimen_fiscal_emisor || r.regimen_fiscal_receptor || '?'; + perRegimen[reg] = (perRegimen[reg] || 0) + aporte; + + const fe = r.fecha_emision?.toISOString?.()?.slice(0, 10) || r.fecha_emision; + const rel07 = r.cfdi_tipo_relacion === '07' ? ' [07]' : ''; + const src = r.source === 'facturapi' ? ' [facturapi]' : ''; + console.log( + ` ${fe} ${r.tipo_comprobante}/${r.metodo_pago}${rel07}${src} ` + + `reg=${reg} ${String(r.rfc_emisor).padEnd(14)}→${String(r.rfc_receptor).padEnd(14)} ` + + `total=${Number(r.total_mxn || 0).toFixed(2).padStart(10)} ` + + `aporte=${aporte.toFixed(2).padStart(10)} ${r.uuid.substring(0,8)}` + ); + } + + console.log(`\n=== Suma de aportes del drill-down: ${sumDrill.toFixed(2)} ===`); + console.log(`Por régimen (drill-down):`); + for (const [reg, monto] of Object.entries(perRegimen).sort()) { + console.log(` ${reg}: ${monto.toFixed(2)}`); + } + + console.log(`\n=== Diferencia KPI − drill: ${(kpi.total - sumDrill).toFixed(2)} ===`); + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/debug-ncs.ts b/apps/api/scripts/debug-ncs.ts new file mode 100644 index 0000000..a26ea60 --- /dev/null +++ b/apps/api/scripts/debug-ncs.ts @@ -0,0 +1,34 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const t = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO7JE8BZ_VDOPR' } }); + if (!t) { console.log('Zorro tenant no encontrado'); return; } + const pool = await tenantDb.getPool(t.id, t.databaseName); + + console.log('--- E PUE EMITIDAS (cualquier fecha) ---'); + const emit = await pool.query(` + SELECT EXTRACT(year FROM fecha_emision) as anio, + regimen_fiscal_emisor, count(*) as n, + SUM(total_mxn)::numeric(14,2) as total + FROM cfdis + WHERE tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND status NOT IN ('Cancelado','0') + GROUP BY 1, 2 ORDER BY 1 DESC, 2 + `); + console.table(emit.rows); + + console.log('\n--- E PUE RECIBIDAS (cualquier fecha) ---'); + const rec = await pool.query(` + SELECT EXTRACT(year FROM fecha_emision) as anio, + regimen_fiscal_receptor, count(*) as n, + SUM(total_mxn)::numeric(14,2) as total + FROM cfdis + WHERE tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND status NOT IN ('Cancelado','0') + GROUP BY 1, 2 ORDER BY 1 DESC, 2 + `); + console.table(rec.rows); + + await prisma.$disconnect(); +} +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/debug-p-mayo.ts b/apps/api/scripts/debug-p-mayo.ts new file mode 100644 index 0000000..2f8aa27 --- /dev/null +++ b/apps/api/scripts/debug-p-mayo.ts @@ -0,0 +1,67 @@ +/** + * Diseca 2 complementos P de Horux 360 que el usuario espera ver en mayo + * pero no aparecen. Verifica fecha_emision vs fecha_pago_p para entender + * en qué mes los está sumando el cálculo. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, nombre: true, databaseName: true }, + }); + + const UUIDS = [ + 'CFACB97E-5426-48D4-A3B9-06B5D160F307', + '384CF943-EFB0-475A-B6B6-240E96088B37', + ]; + + // Loop por todos los tenants + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + console.log(`\n>>> Tenant: ${t.rfc} (${t.nombre}) <<<`); + + for (const uuid of UUIDS) { + const { rows: [c] } = await pool.query(` + SELECT uuid, type, tipo_comprobante, metodo_pago, forma_pago, uso_cfdi, + cfdi_tipo_relacion, + total_mxn, monto_pago_mxn, iva_traslado_pago_mxn, + rfc_emisor, regimen_fiscal_emisor, + rfc_receptor, regimen_fiscal_receptor, + fecha_emision, fecha_pago_p, status + FROM cfdis WHERE LOWER(uuid) = LOWER($1) + `, [uuid]); + + console.log(`\n═══ CFDI ${uuid} ═══`); + if (!c) { console.log(' (NO ENCONTRADO en BD de Horux 360)'); continue; } + + console.log(` Tipo: ${c.tipo_comprobante} ${c.metodo_pago || ''}`); + console.log(` Status: ${c.status}`); + console.log(` type (lado): ${c.type}`); + console.log(` rfc_emisor: ${c.rfc_emisor} (régimen ${c.regimen_fiscal_emisor})`); + console.log(` rfc_receptor: ${c.rfc_receptor} (régimen ${c.regimen_fiscal_receptor})`); + console.log(` total_mxn: $${c.total_mxn}`); + console.log(` monto_pago_mxn: $${c.monto_pago_mxn}`); + console.log(` iva_traslado_pago: $${c.iva_traslado_pago_mxn}`); + console.log(` ──────────────────────────────────────`); + console.log(` fecha_emision: ${c.fecha_emision}`); + console.log(` fecha_pago_p: ${c.fecha_pago_p}`); + console.log(` ──────────────────────────────────────`); + + // Análisis: en qué mes "cae" según el cálculo de ingresos (Grupo 1 — FR_PAGO usa fecha_pago_p) + const fecPago = c.fecha_pago_p ? new Date(c.fecha_pago_p) : null; + const fecEmi = c.fecha_emision ? new Date(c.fecha_emision) : null; + if (fecPago) { + console.log(` En cálculo Ingresos: APARECE EN ${fecPago.getFullYear()}-${String(fecPago.getMonth() + 1).padStart(2, '0')}`); + console.log(` (filtro: fecha_pago_p)`); + } + if (fecEmi) { + console.log(` En filtros UI fecha: se EMITIÓ en ${fecEmi.getFullYear()}-${String(fecEmi.getMonth() + 1).padStart(2, '0')}`); + } + } + } // close tenant loop + + await prisma.$disconnect(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/apps/api/scripts/decrypt-fiel.ts b/apps/api/scripts/decrypt-fiel.ts new file mode 100644 index 0000000..c960b0a --- /dev/null +++ b/apps/api/scripts/decrypt-fiel.ts @@ -0,0 +1,82 @@ +/** + * CLI script to decrypt FIEL credentials from filesystem backup. + * Usage: FIEL_ENCRYPTION_KEY= npx tsx scripts/decrypt-fiel.ts + * + * Decrypted files are written to /tmp/horux-fiel-/ and auto-deleted after 30 minutes. + */ +import { readFile, writeFile, mkdir, rm } from 'fs/promises'; +import { join } from 'path'; +import { createDecipheriv, createHash } from 'crypto'; + +const FIEL_PATH = process.env.FIEL_STORAGE_PATH || '/var/horux/fiel'; +const FIEL_KEY = process.env.FIEL_ENCRYPTION_KEY; + +const rfc = process.argv[2]; +if (!rfc) { + console.error('Usage: FIEL_ENCRYPTION_KEY= npx tsx scripts/decrypt-fiel.ts '); + process.exit(1); +} +if (!FIEL_KEY) { + console.error('Error: FIEL_ENCRYPTION_KEY environment variable is required'); + process.exit(1); +} + +function deriveKey(): Buffer { + return createHash('sha256').update(FIEL_KEY!).digest(); +} + +function decryptBuffer(encrypted: Buffer, iv: Buffer, tag: Buffer): Buffer { + const key = deriveKey(); + const decipher = createDecipheriv('aes-256-gcm', key, iv); + decipher.setAuthTag(tag); + return Buffer.concat([decipher.update(encrypted), decipher.final()]); +} + +async function main() { + const fielDir = join(FIEL_PATH, rfc.toUpperCase()); + const outputDir = `/tmp/horux-fiel-${rfc.toUpperCase()}`; + + console.log(`Reading encrypted FIEL from: ${fielDir}`); + + // Read encrypted certificate + const cerEnc = await readFile(join(fielDir, 'certificate.cer.enc')); + const cerIv = await readFile(join(fielDir, 'certificate.cer.iv')); + const cerTag = await readFile(join(fielDir, 'certificate.cer.tag')); + + // Read encrypted private key + const keyEnc = await readFile(join(fielDir, 'private_key.key.enc')); + const keyIv = await readFile(join(fielDir, 'private_key.key.iv')); + const keyTag = await readFile(join(fielDir, 'private_key.key.tag')); + + // Read and decrypt metadata + const metaEnc = await readFile(join(fielDir, 'metadata.json.enc')); + const metaIv = await readFile(join(fielDir, 'metadata.json.iv')); + const metaTag = await readFile(join(fielDir, 'metadata.json.tag')); + + // Decrypt all + const cerData = decryptBuffer(cerEnc, cerIv, cerTag); + const keyData = decryptBuffer(keyEnc, keyIv, keyTag); + const metadata = JSON.parse(decryptBuffer(metaEnc, metaIv, metaTag).toString('utf-8')); + + // Write decrypted files + await mkdir(outputDir, { recursive: true, mode: 0o700 }); + await writeFile(join(outputDir, 'certificate.cer'), cerData, { mode: 0o600 }); + await writeFile(join(outputDir, 'private_key.key'), keyData, { mode: 0o600 }); + await writeFile(join(outputDir, 'metadata.json'), JSON.stringify(metadata, null, 2), { mode: 0o600 }); + + console.log(`\nDecrypted files written to: ${outputDir}`); + console.log('Metadata:', metadata); + console.log('\nFiles will be auto-deleted in 30 minutes.'); + + // Auto-delete after 30 minutes + setTimeout(async () => { + await rm(outputDir, { recursive: true, force: true }); + console.log(`Cleaned up ${outputDir}`); + process.exit(0); + }, 30 * 60 * 1000); +} + +main().catch((err) => { + console.error('Failed to decrypt FIEL:', err.message); + process.exit(1); +}); diff --git a/apps/api/scripts/deep-egresos.ts b/apps/api/scripts/deep-egresos.ts new file mode 100644 index 0000000..20a34c3 --- /dev/null +++ b/apps/api/scripts/deep-egresos.ts @@ -0,0 +1,101 @@ +/** + * Compara paso a paso los 3 componentes del cálculo de egresos 612 en Feb 2025: + * 1) Query exacto que usa calcularEgresosPorRegimen (con FECHA_RANGO / FECHA_PAGO_RANGO) + * 2) Vs el drill-down usando fecha efectiva por fila + * Detalle al CFDI para encontrar discrepancias. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const fi = '2025-02-01'; + const ff = '2025-02-28'; + const contrib = 'd745a915-6a23-4818-944b-a7e1e18e536a'; + const reg = '612'; + const IMP_TRAS = `COALESCE(iva_traslado_mxn,0) + COALESCE(ieps_traslado_mxn,0) + COALESCE(impuestos_locales_trasladado_mxn,0)`; + const IMP_TRAS_PAGO = `COALESCE(iva_traslado_pago_mxn,0) + COALESCE(ieps_traslado_pago_mxn,0)`; + const EXCL = `COALESCE((SELECT SUM(COALESCE(cc.importe_mxn,0) - COALESCE(cc.descuento_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ('84121603','93161608','85101501','85121800')), 0)`; + + // QUERY 1 FACTURAS (idéntico a calcularEgresosPorRegimen) + const f = await pool.query( + `SELECT uuid, total_mxn, (${IMP_TRAS}) AS imp, (${EXCL}) AS excl, + COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL}) AS neto, + cfdi_tipo_relacion AS rel + FROM cfdis + WHERE type='RECIBIDO' AND tipo_comprobante='I' AND metodo_pago='PUE' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + AND regimen_fiscal_receptor = $3 + AND contribuyente_id = $4 + ORDER BY fecha_emision`, + [fi, ff, reg, contrib], + ); + const sumF = f.rows.reduce((s, r) => s + Number(r.neto), 0); + console.log(`FACTURAS I PUE reg=${reg}: n=${f.rows.length} sum_neto=${sumF.toFixed(2)}`); + + // QUERY 2 PAGOS P + const p = await pool.query( + `SELECT uuid, monto_pago_mxn, (${IMP_TRAS_PAGO}) AS imp, + COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO}) AS neto, + fecha_pago_p, fecha_emision + FROM cfdis + WHERE type='RECIBIDO' AND tipo_comprobante='P' + AND status NOT IN ('Cancelado','0') + AND fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day') + AND regimen_fiscal_receptor = $3 + AND contribuyente_id = $4 + ORDER BY fecha_pago_p`, + [fi, ff, reg, contrib], + ); + const sumP = p.rows.reduce((s, r) => s + Number(r.neto), 0); + console.log(`PAGOS P reg=${reg} (fecha_pago_p): n=${p.rows.length} sum_neto=${sumP.toFixed(2)}`); + + // También probar con fecha_emision del P (alternativo) + const pEmis = await pool.query( + `SELECT uuid, COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO}) AS neto, + fecha_pago_p, fecha_emision + FROM cfdis + WHERE type='RECIBIDO' AND tipo_comprobante='P' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + AND regimen_fiscal_receptor = $3 + AND contribuyente_id = $4 + ORDER BY fecha_emision`, + [fi, ff, reg, contrib], + ); + const sumPe = pEmis.rows.reduce((s, r) => s + Number(r.neto), 0); + console.log(` (alt) PAGOS P filtrados por fecha_emision: n=${pEmis.rows.length} sum_neto=${sumPe.toFixed(2)}`); + + // QUERY 3 NC + const n = await pool.query( + `SELECT uuid, total_mxn, (${IMP_TRAS}) AS imp, (${EXCL}) AS excl, + COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL}) AS neto, + cfdi_tipo_relacion AS rel + FROM cfdis + WHERE type='RECIBIDO' AND tipo_comprobante='E' AND metodo_pago='PUE' + AND COALESCE(cfdi_tipo_relacion,'') <> '07' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + AND regimen_fiscal_receptor = $3 + AND contribuyente_id = $4`, + [fi, ff, reg, contrib], + ); + const sumN = n.rows.reduce((s, r) => s + Number(r.neto), 0); + console.log(`NC E PUE excl 07 reg=${reg}: n=${n.rows.length} sum_neto=${sumN.toFixed(2)}`); + + console.log(`\nTotal ON-THE-FLY (reg 612): ${(sumF + sumP - sumN).toFixed(2)}`); + console.log(`Cache dice: 446180.10`); + console.log(`Delta: ${((sumF + sumP - sumN) - 446180.10).toFixed(2)}`); + + // Detalle de los P para investigar — fecha_emision vs fecha_pago_p + console.log(`\nDetalle PAGOS P (filtrados por fecha_pago_p):`); + for (const r of p.rows) { + console.log(` ${r.uuid.substring(0,8)} monto=${Number(r.monto_pago_mxn).toFixed(2)} neto=${Number(r.neto).toFixed(2)} fecha_pago_p=${r.fecha_pago_p?.toISOString?.()?.slice(0,10)} fecha_emision=${r.fecha_emision?.toISOString?.()?.slice(0,10)}`); + } + + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/detail-ingresos.ts b/apps/api/scripts/detail-ingresos.ts new file mode 100644 index 0000000..a03a02b --- /dev/null +++ b/apps/api/scripts/detail-ingresos.ts @@ -0,0 +1,55 @@ +/** Detalle neto de cada CFDI del dashboard para Horux 360 mayo 2025. */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { resolveContribuyenteContext } from '../src/utils/contribuyente-context.js'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + const ctx = await resolveContribuyenteContext(pool, tenant.id, 'b3761db6-0b8d-4251-8078-4ddc31e9c75b'); + + // Facturas I PUE (rendición con la misma lógica de g1Facturas) + const { rows: fact } = await pool.query( + `SELECT uuid, total_mxn, + iva_traslado_mxn, ieps_traslado_mxn, impuestos_locales_trasladado_mxn, + iva_retencion_mxn, isr_retencion_mxn, ieps_retencion_mxn, impuestos_locales_retenidos_mxn, + cfdi_tipo_relacion, + (COALESCE(total_mxn,0) - COALESCE(iva_traslado_mxn,0) - COALESCE(ieps_traslado_mxn,0) - COALESCE(impuestos_locales_trasladado_mxn,0)) AS neto_normal + FROM cfdis + WHERE ${ctx.esEmisor} AND tipo_comprobante='I' AND metodo_pago='PUE' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= '2025-05-01'::date AND fecha_emision < '2025-05-31'::date + interval '1 day' + AND regimen_fiscal_emisor = '626' + ORDER BY fecha_emision`, + ); + console.log(`\nI PUE régimen 626:`); + for (const r of fact) { + console.log(` ${r.uuid.substring(0,8)} total=${Number(r.total_mxn).toFixed(2)} iva_tras=${Number(r.iva_traslado_mxn).toFixed(2)} iva_ret=${Number(r.iva_retencion_mxn).toFixed(2)} isr_ret=${Number(r.isr_retencion_mxn).toFixed(2)} neto=${Number(r.neto_normal).toFixed(2)} rel=${r.cfdi_tipo_relacion || '-'}`); + } + const factNeto = fact.reduce((s, r) => s + Number(r.neto_normal), 0); + console.log(` Suma neto facturas: ${factNeto.toFixed(2)}`); + + // Pagos P + const { rows: pagos } = await pool.query( + `SELECT uuid, fecha_pago_p, monto_pago_mxn, + iva_traslado_pago_mxn, ieps_traslado_pago_mxn, + iva_retencion_pago_mxn, isr_retencion_pago_mxn, ieps_retencion_pago_mxn, + (COALESCE(monto_pago_mxn,0) - COALESCE(iva_traslado_pago_mxn,0) - COALESCE(ieps_traslado_pago_mxn,0)) AS neto_normal + FROM cfdis + WHERE ${ctx.esEmisor} AND tipo_comprobante='P' + AND status NOT IN ('Cancelado','0') + AND fecha_pago_p >= '2025-05-01'::date AND fecha_pago_p < '2025-05-31'::date + interval '1 day' + AND regimen_fiscal_emisor = '626' + ORDER BY fecha_pago_p`, + ); + console.log(`\nPagos P régimen 626:`); + for (const r of pagos) { + console.log(` ${r.uuid.substring(0,8)} monto_pago=${Number(r.monto_pago_mxn).toFixed(2)} iva_tras_pago=${Number(r.iva_traslado_pago_mxn).toFixed(2)} iva_ret_pago=${Number(r.iva_retencion_pago_mxn).toFixed(2)} neto=${Number(r.neto_normal).toFixed(2)}`); + } + const pagosNeto = pagos.reduce((s, r) => s + Number(r.neto_normal), 0); + console.log(` Suma neto pagos: ${pagosNeto.toFixed(2)}`); + + console.log(`\nTOTAL facturas + pagos: ${(factNeto + pagosNeto).toFixed(2)}`); + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/detail-iva-mes.ts b/apps/api/scripts/detail-iva-mes.ts new file mode 100644 index 0000000..ca4df3f --- /dev/null +++ b/apps/api/scripts/detail-iva-mes.ts @@ -0,0 +1,68 @@ +/** Breakdown: qué CFDIs contribuyen al IVA acreditable vs al gasto. */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { resolveContribuyenteContext } from '../src/utils/contribuyente-context.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'd745a915-6a23-4818-944b-a7e1e18e536a'; +const yearMonth = process.argv[4] || '2025-12'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + const ctx = await resolveContribuyenteContext(pool, tenant.id, contribuyenteId); + + const [anio, mes] = yearMonth.split('-').map(Number); + const lastDay = new Date(anio, mes, 0).getDate(); + const fi = `${yearMonth}-01`; + const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`; + + const IMP_TRAS = `COALESCE(iva_traslado_mxn,0) + COALESCE(ieps_traslado_mxn,0) + COALESCE(impuestos_locales_trasladado_mxn,0)`; + + // I PUE recibidas + const { rows: facturas } = await pool.query( + `SELECT uuid, total_mxn, iva_traslado_mxn, cfdi_tipo_relacion, cfdis_relacionados, + (COALESCE(total_mxn,0) - (${IMP_TRAS})) AS neto_normal + FROM cfdis + WHERE ${ctx.esReceptor} AND tipo_comprobante='I' AND metodo_pago='PUE' + AND status NOT IN ('Cancelado','0') + AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day') + ORDER BY total_mxn DESC`, + [fi, ff], + ); + + console.log(`\n=== I PUE recibidas ${yearMonth} ===`); + console.log(`# | UUID | total | IVA | neto_normal | rel | cfdis_relacionados`); + for (const r of facturas) { + const rel = r.cfdi_tipo_relacion || '-'; + const cr = r.cfdis_relacionados ? ` → ${r.cfdis_relacionados.substring(0,36)}` : ''; + console.log(` ${r.uuid.substring(0,8)} total=${Number(r.total_mxn).toFixed(2).padStart(12)} IVA=${Number(r.iva_traslado_mxn).toFixed(2).padStart(10)} neto=${Number(r.neto_normal).toFixed(2).padStart(12)} rel=${rel.padEnd(3)}${cr}`); + } + + // I PUE recibidas con relación 07 — verificar si el anticipo está en otro mes + const i07 = facturas.filter((r: any) => r.cfdi_tipo_relacion === '07'); + if (i07.length > 0) { + console.log(`\nI/07 recibidas en ${yearMonth}: ${i07.length}`); + for (const r of i07) { + const relsUuids = (r.cfdis_relacionados || '').split('|').filter(Boolean).map((u: string) => u.toLowerCase()); + if (relsUuids.length > 0) { + const { rows: rels } = await pool.query( + `SELECT uuid, fecha_emision, total_mxn, iva_traslado_mxn + FROM cfdis a + WHERE LOWER(a.uuid) = ANY($1::text[]) + AND a.status NOT IN ('Cancelado','0')`, + [relsUuids], + ); + console.log(`\n I/07 ${r.uuid.substring(0,8)} total=${Number(r.total_mxn).toFixed(2)} IVA=${Number(r.iva_traslado_mxn).toFixed(2)}`); + for (const a of rels) { + const fecha = a.fecha_emision.toISOString().slice(0,10); + const fuera = fecha.substring(0,7) !== yearMonth ? ' ← FUERA DEL MES' : ''; + console.log(` anticipo ${a.uuid.substring(0,8)} fecha=${fecha} total=${Number(a.total_mxn).toFixed(2)} IVA=${Number(a.iva_traslado_mxn).toFixed(2)}${fuera}`); + } + } + } + } + + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/drill-ingresos.ts b/apps/api/scripts/drill-ingresos.ts new file mode 100644 index 0000000..b806024 --- /dev/null +++ b/apps/api/scripts/drill-ingresos.ts @@ -0,0 +1,88 @@ +/** + * Simula el drill-down bucket=ingresos para un contribuyente/mes y muestra + * cada CFDI que aparecería en el drill. Permite comparar con el total del + * dashboard. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { resolveContribuyenteContext } from '../src/utils/contribuyente-context.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'b3761db6-0b8d-4251-8078-4ddc31e9c75b'; +const yearMonth = process.argv[4] || '2025-05'; + +const GRUPO_PF_EMPRESARIAL = ['606', '612', '621', '625', '626']; +const GRUPO_PM_OTROS = ['601', '603', '607', '608', '610', '611', '614', '615', '620', '622', '623', '624']; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const [anio, mes] = yearMonth.split('-').map(Number); + const lastDay = new Date(anio, mes, 0).getDate(); + const fi = `${yearMonth}-01`; + const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`; + + const ctx = await resolveContribuyenteContext(pool, tenant.id, contribuyenteId); + const esEmisor = ctx.esEmisor; + const esReceptor = ctx.esReceptor; + const g1 = GRUPO_PF_EMPRESARIAL.map(r => `'${r}'`).join(','); + const g3 = GRUPO_PM_OTROS.map(r => `'${r}'`).join(','); + + const FECHA_EFECTIVA = `CASE WHEN tipo_comprobante = 'P' THEN fecha_pago_p ELSE fecha_emision END`; + + // Query idéntico al drill-down bucket=ingresos + const { rows } = await pool.query( + `SELECT uuid, tipo_comprobante, metodo_pago, + regimen_fiscal_emisor, regimen_fiscal_receptor, + cfdi_tipo_relacion, + total_mxn, monto_pago_mxn, + fecha_emision, fecha_pago_p + FROM cfdis + WHERE 1=1 + AND ( + ( + ${esEmisor} + AND regimen_fiscal_emisor IN (${g1}) + AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE') + OR tipo_comprobante = 'P' + OR (tipo_comprobante = 'E' AND metodo_pago = 'PUE' AND COALESCE(cfdi_tipo_relacion, '') <> '07') + ) + ) + OR ( + ${esReceptor} + AND tipo_comprobante = 'N' AND metodo_pago = 'PUE' + AND regimen_fiscal_receptor = '605' + ) + OR ( + ${esEmisor} + AND regimen_fiscal_emisor IN (${g3}) + AND ( + (tipo_comprobante = 'I' AND metodo_pago IN ('PUE','PPD')) + OR (tipo_comprobante = 'E' AND metodo_pago = 'PUE') + ) + ) + ) + AND status NOT IN ('Cancelado','0') + AND ${FECHA_EFECTIVA} >= $1::date + AND ${FECHA_EFECTIVA} < ($2::date + interval '1 day') + ORDER BY ${FECHA_EFECTIVA}`, + [fi, ff], + ); + + console.log(`\n=== Drill bucket=ingresos ${yearMonth} contrib=${ctx.rfc} ===`); + console.log(`Filas: ${rows.length}\n`); + let sumTotal = 0, sumPago = 0; + for (const r of rows) { + console.log(` ${r.uuid.substring(0,8)} ${r.tipo_comprobante}${r.metodo_pago ? '/' + r.metodo_pago : ''}${r.cfdi_tipo_relacion ? ' rel=' + r.cfdi_tipo_relacion : ''} reg=${r.regimen_fiscal_emisor || r.regimen_fiscal_receptor} total=${Number(r.total_mxn || 0).toFixed(2)} pago=${Number(r.monto_pago_mxn || 0).toFixed(2)}`); + sumTotal += Number(r.total_mxn || 0); + sumPago += Number(r.monto_pago_mxn || 0); + } + console.log(`\nSuma total_mxn (bruto drill): ${sumTotal.toFixed(2)}`); + console.log(`Suma monto_pago_mxn: ${sumPago.toFixed(2)}`); + console.log(`(Total bruto cuenta I + E a total, y P a monto_pago)`); + + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/extract-terminos.mjs b/apps/api/scripts/extract-terminos.mjs new file mode 100644 index 0000000..b238836 --- /dev/null +++ b/apps/api/scripts/extract-terminos.mjs @@ -0,0 +1,79 @@ +#!/usr/bin/env node +/** + * Extrae el texto del PDF de términos y condiciones y lo convierte en un + * módulo TypeScript para que el frontend lo renderice sin tener que parsear + * el PDF en runtime. + * + * Además copia el PDF original a `apps/web/public/legal/` para servirlo como + * descarga. + * + * Uso: + * pnpm legal:sync + * + * Cuando se actualiza el documento legal: + * 1. Reemplazar `docs/legal/Terminos y condiciones.pdf` por la nueva versión + * (mismo nombre de archivo). + * 2. Correr `pnpm legal:sync`. + * 3. Commit de los cambios (PDF, terminos.ts, PDF copy). + */ +import { readFileSync, writeFileSync, copyFileSync, mkdirSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { PDFParse } from 'pdf-parse'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const ROOT = resolve(__dirname, '../../../'); +const SRC_PDF = resolve(ROOT, 'docs/legal/Terminos y condiciones.pdf'); +const DEST_PDF = resolve(ROOT, 'apps/web/public/legal/terminos-y-condiciones.pdf'); +const DEST_TS = resolve(ROOT, 'apps/web/content/terminos.ts'); + +async function main() { + console.log('[legal:sync] Leyendo:', SRC_PDF); + const buf = readFileSync(SRC_PDF); + + const parser = new PDFParse({ data: buf }); + const textResult = await parser.getText(); + await parser.destroy(); + + const rawText = (textResult.text ?? '').trim(); + const pages = textResult.total ?? textResult.pages?.length ?? 0; + + if (!rawText) { + console.error('[legal:sync] ERROR: el PDF no contiene texto extraíble (¿escaneado sin OCR?).'); + process.exit(1); + } + + // Copia el PDF a public/ para que sea descargable + mkdirSync(dirname(DEST_PDF), { recursive: true }); + copyFileSync(SRC_PDF, DEST_PDF); + + // Escribe el texto como módulo TypeScript. Escapa backticks para que el + // template literal no rompa si el PDF los contiene. + const escaped = rawText.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${'); + const extractedAt = new Date().toISOString(); + const content = `// AUTO-GENERADO por \`pnpm legal:sync\`. NO editar a mano. +// Fuente: docs/legal/Terminos y condiciones.pdf +// Regenerar tras actualizar el PDF. + +export const TERMINOS_TEXT = \`${escaped}\`; + +export const TERMINOS_META = { + extractedAt: '${extractedAt}', + pages: ${pages}, + chars: ${rawText.length}, +} as const; +`; + + mkdirSync(dirname(DEST_TS), { recursive: true }); + writeFileSync(DEST_TS, content, 'utf8'); + + console.log(`[legal:sync] OK: ${rawText.length} chars extraídos, ${pages} páginas.`); + console.log(`[legal:sync] → ${DEST_PDF}`); + console.log(`[legal:sync] → ${DEST_TS}`); +} + +main().catch(err => { + console.error('[legal:sync] FAIL:', err); + process.exit(1); +}); diff --git a/apps/api/scripts/find-contribuyente.ts b/apps/api/scripts/find-contribuyente.ts new file mode 100644 index 0000000..53cea81 --- /dev/null +++ b/apps/api/scripts/find-contribuyente.ts @@ -0,0 +1,28 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const term = (process.argv[2] || '').toLowerCase(); +if (!term) { console.error('Usage: tsx scripts/find-contribuyente.ts '); process.exit(1); } + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows: cols } = await pool.query( + `SELECT column_name FROM information_schema.columns WHERE table_name='contribuyentes'`, + ); + const colNames = cols.map((c: any) => c.column_name); + const nameCols = colNames.filter(n => n.includes('nombre') || n.includes('razon')); + const filterSql = nameCols.map(c => `LOWER(${c}) LIKE '%${term}%'`).join(' OR '); + if (!filterSql) continue; + const { rows } = await pool.query(`SELECT entidad_id, rfc, ${nameCols.join(',')} FROM contribuyentes WHERE ${filterSql}`); + if (rows.length > 0) { + console.log(`\n[${t.rfc}]`); + for (const r of rows) console.log(r); + } + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/find-i07-ppd-cases.ts b/apps/api/scripts/find-i07-ppd-cases.ts new file mode 100644 index 0000000..362b115 --- /dev/null +++ b/apps/api/scripts/find-i07-ppd-cases.ts @@ -0,0 +1,75 @@ +/** + * Encuentra E que referencien directamente a una I/07 PPD vía + * `cfdis_relacionados`. Patrón real observado: la E "ajusta" la I/07 PPD, + * no al anticipo original. La I/07 PPD apunta al anticipo, la E apunta a + * la I/07 PPD. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +const TARGET_RFC = process.argv[2]; + +async function main() { + const tenants = await prisma.tenant.findMany({ select: { id: true, rfc: true, databaseName: true } }); + + for (const t of tenants) { + let pool; + try { pool = await tenantDb.getPool(t.id, t.databaseName); } catch { continue; } + console.log(`\n=== ${t.rfc}${TARGET_RFC ? ` (RFC=${TARGET_RFC})` : ''} ===`); + + const rfcFilter = TARGET_RFC + ? `AND (UPPER(i.rfc_emisor) = UPPER('${TARGET_RFC}') OR UPPER(i.rfc_receptor) = UPPER('${TARGET_RFC}'))` + : ''; + + const { rows } = await pool.query(` + SELECT + i.uuid AS i_uuid, i.fecha_emision AS i_fecha, i.total_mxn AS i_total, + i.iva_traslado_mxn AS i_iva, i.rfc_emisor AS i_emisor, i.rfc_receptor AS i_receptor, + i.type AS i_type, + e.uuid AS e_uuid, e.cfdi_tipo_relacion AS e_rel, e.metodo_pago AS e_mp, + e.fecha_emision AS e_fecha, e.total_mxn AS e_total, e.iva_traslado_mxn AS e_iva, + ABS(EXTRACT(EPOCH FROM (e.fecha_emision - i.fecha_emision)) / 86400)::int AS diff_dias, + EXTRACT(YEAR FROM i.fecha_emision)::int * 12 + EXTRACT(MONTH FROM i.fecha_emision)::int AS i_periodo, + EXTRACT(YEAR FROM e.fecha_emision)::int * 12 + EXTRACT(MONTH FROM e.fecha_emision)::int AS e_periodo + FROM cfdis i + JOIN cfdis e + ON LOWER(i.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + WHERE i.cfdi_tipo_relacion = '07' + AND i.tipo_comprobante = 'I' AND i.metodo_pago = 'PPD' + AND i.status NOT IN ('Cancelado','0') + AND e.tipo_comprobante = 'E' + AND e.status NOT IN ('Cancelado','0') + ${rfcFilter} + ORDER BY i.fecha_emision DESC + `); + + console.log(`Total pares: ${rows.length}`); + + const buckets = { mismoMes: 0, eDespues1: 0, eDespuesMas: 0, eAntes: 0 }; + for (const r of rows) { + const diff = Number(r.e_periodo) - Number(r.i_periodo); + if (diff < 0) buckets.eAntes++; + else if (diff === 0) buckets.mismoMes++; + else if (diff === 1) buckets.eDespues1++; + else buckets.eDespuesMas++; + } + console.log(` Mismo mes: ${buckets.mismoMes}`); + console.log(` E 1 mes después: ${buckets.eDespues1}`); + console.log(` E ≥2 meses después: ${buckets.eDespuesMas}`); + console.log(` E antes: ${buckets.eAntes}`); + + if (rows.length > 0) { + console.log(`\n Detalle (top ${Math.min(rows.length, 10)}):`); + for (const r of rows.slice(0, 10)) { + const fi = new Date(r.i_fecha).toISOString().slice(0, 10); + const fe = new Date(r.e_fecha).toISOString().slice(0, 10); + const i_base = Number(r.i_total) - Number(r.i_iva || 0); + const e_base = Number(r.e_total) - Number(r.e_iva || 0); + const diff = Number(r.e_periodo) - Number(r.i_periodo); + console.log(` I/07 PPD ${r.i_uuid.substring(0,8)} ${fi} base=${i_base.toFixed(2)} ${r.i_emisor}→${r.i_receptor} (${r.i_type})`); + console.log(` E/${r.e_rel ?? 'null'}/${r.e_mp || '?'} ${r.e_uuid.substring(0,8)} ${fe} base=${e_base.toFixed(2)} diffMeses=${diff} (${r.diff_dias}d)`); + } + } + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/find-uuid.ts b/apps/api/scripts/find-uuid.ts new file mode 100644 index 0000000..cb83100 --- /dev/null +++ b/apps/api/scripts/find-uuid.ts @@ -0,0 +1,12 @@ +import { prisma, tenantDb } from '../src/config/database.js'; +const prefix = process.argv[2]; +async function main() { + const ts = await prisma.tenant.findMany({ where: { active: true }, select: { id: true, rfc: true, databaseName: true } }); + for (const t of ts) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows } = await pool.query(`SELECT uuid FROM cfdis WHERE uuid LIKE $1 || '%'`, [prefix]); + for (const r of rows) console.log(t.rfc, r.uuid); + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/import-lista-negra.ts b/apps/api/scripts/import-lista-negra.ts new file mode 100644 index 0000000..fe251c1 --- /dev/null +++ b/apps/api/scripts/import-lista-negra.ts @@ -0,0 +1,104 @@ +import { PrismaClient } from '@prisma/client'; +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const prisma = new PrismaClient(); + +const SITUACIONES_VALIDAS = ['Definitivo', 'Presunto', 'Desvirtuado', 'Sentencia Favorable']; + +function parseCsvLine(line: string): string[] { + const fields: string[] = []; + let current = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const c = line[i]; + if (c === '"') { + if (inQuotes && line[i + 1] === '"') { + current += '"'; + i++; + } else { + inQuotes = !inQuotes; + } + } else if (c === ',' && !inQuotes) { + fields.push(current.trim()); + current = ''; + } else { + current += c; + } + } + fields.push(current.trim()); + return fields; +} + +async function main() { + const filePath = resolve(__dirname, '..', '..', '..', 'lista_negra', 'Listado_completo_69-B.csv'); + console.log('📂 Leyendo:', filePath); + + const data = readFileSync(filePath, 'latin1'); + const lines = data.split('\n'); + console.log(`📄 ${lines.length} líneas en el archivo`); + + // Parsear registros (saltar headers: líneas 0, 1, 2) + const registros: { rfc: string; nombre: string; situacion: string }[] = []; + + for (let i = 3; i < lines.length; i++) { + const line = lines[i].replace(/\r/g, '').trim(); + if (!line) continue; + + const fields = parseCsvLine(line); + if (fields.length < 4) continue; + + const rfc = fields[1]?.trim(); + const nombre = fields[2]?.trim(); + const situacion = fields[3]?.trim(); + + if (!rfc || !rfc.match(/^[A-Z0-9&]{10,13}$/)) continue; + if (!SITUACIONES_VALIDAS.includes(situacion)) continue; + + registros.push({ rfc, nombre, situacion }); + } + + console.log(`✅ ${registros.length} registros válidos parseados`); + + // Contar por situación + const counts: Record = {}; + for (const r of registros) { + counts[r.situacion] = (counts[r.situacion] || 0) + 1; + } + console.log(' Situaciones:', counts); + + // Sincronizar: limpiar y reinsertar todo + console.log('🔄 Sincronizando con base de datos...'); + + await prisma.listaNegra.deleteMany(); + + // Insertar en batches de 500 + const BATCH = 500; + let inserted = 0; + + for (let i = 0; i < registros.length; i += BATCH) { + const batch = registros.slice(i, i + BATCH); + + // Deduplicar por RFC (quedarse con el último) + const unique = new Map(); + for (const r of batch) unique.set(r.rfc, r); + + await prisma.listaNegra.createMany({ + data: Array.from(unique.values()), + skipDuplicates: true, + }); + + inserted += unique.size; + if ((i + BATCH) % 5000 === 0 || i + BATCH >= registros.length) { + console.log(` ${Math.min(i + BATCH, registros.length)}/${registros.length}...`); + } + } + + const total = await prisma.listaNegra.count(); + console.log(`\n🎉 Lista negra actualizada: ${total} registros en la base de datos`); +} + +main() + .catch(console.error) + .finally(() => prisma.$disconnect()); diff --git a/apps/api/scripts/inspect-cfdi-full.ts b/apps/api/scripts/inspect-cfdi-full.ts new file mode 100644 index 0000000..63024e9 --- /dev/null +++ b/apps/api/scripts/inspect-cfdi-full.ts @@ -0,0 +1,26 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const rawUuid = process.argv[2]; +if (!rawUuid) { console.error('Usage: tsx scripts/inspect-cfdi-full.ts '); process.exit(1); } +const uuid = rawUuid.toLowerCase(); + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows } = await pool.query( + `SELECT * FROM cfdis WHERE LOWER(uuid) = $1`, + [uuid], + ); + if (rows.length === 0) continue; + console.log(`\n[${t.rfc}] CFDI:`); + console.log(rows[0]); + } + + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/inspect-cfdi.ts b/apps/api/scripts/inspect-cfdi.ts new file mode 100644 index 0000000..0d2eb68 --- /dev/null +++ b/apps/api/scripts/inspect-cfdi.ts @@ -0,0 +1,90 @@ +/** + * Inspecciona el estado de un CFDI y sus relacionados (pagos + E/07) en todos + * los tenants. Útil para debug de saldos pendientes. + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/inspect-cfdi.ts + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +const rawUuid = process.argv[2]; +if (!rawUuid) { + console.error('Usage: tsx scripts/inspect-cfdi.ts '); + process.exit(1); +} +const uuid = rawUuid.toLowerCase(); + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + orderBy: { rfc: 'asc' }, + }); + + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + + const { rows: base } = await pool.query( + `SELECT id, uuid, type, tipo_comprobante, metodo_pago, status, fecha_emision, + total, total_mxn, monto_pago, monto_pago_mxn, + saldo_insoluto, saldo_pendiente, saldo_pendiente_mxn, + uuid_relacionado, cfdi_tipo_relacion, cfdis_relacionados, + rfc_emisor, rfc_receptor, conciliado, id_conciliacion, + source, facturapi_id + FROM cfdis WHERE LOWER(uuid) = $1`, + [uuid], + ); + + if (base.length === 0) continue; + + console.log(`\n=== Tenant ${t.rfc} (${t.databaseName}) ===`); + console.log('CFDI base:'); + console.log(base[0]); + + // P complements que apuntan a este UUID via uuid_relacionado (DoctoRelacionado) + const { rows: pagosP } = await pool.query( + `SELECT id, uuid, type, tipo_comprobante, fecha_emision, fecha_pago_p, + monto_pago, monto_pago_mxn, num_parcialidad, + uuid_relacionado, status + FROM cfdis + WHERE tipo_comprobante = 'P' AND LOWER(uuid_relacionado) = $1 + ORDER BY fecha_pago_p NULLS LAST, id`, + [uuid], + ); + console.log(`\nComplementos P que referencian este UUID (DoctoRelacionado): ${pagosP.length}`); + for (const r of pagosP) console.log(' ', r); + + // E CFDIs con cfdis_relacionados que contengan este UUID (TipoRelacion=07 típicamente) + const { rows: ecfdis } = await pool.query( + `SELECT id, uuid, type, tipo_comprobante, metodo_pago, fecha_emision, + total, total_mxn, cfdi_tipo_relacion, cfdis_relacionados, + status + FROM cfdis + WHERE tipo_comprobante = 'E' + AND cfdis_relacionados IS NOT NULL + AND LOWER(cfdis_relacionados) LIKE $1 + ORDER BY fecha_emision, id`, + [`%${uuid}%`], + ); + console.log(`\nCFDIs tipo E con este UUID en cfdis_relacionados: ${ecfdis.length}`); + for (const r of ecfdis) console.log(' ', r); + + // Si el base está conciliado, traer la fila + if (base[0].id_conciliacion) { + const { rows: conc } = await pool.query( + `SELECT * FROM conciliaciones WHERE id = $1`, + [base[0].id_conciliacion], + ); + console.log(`\nConciliación vinculada:`); + for (const r of conc) console.log(' ', r); + } + } + + await prisma.$disconnect(); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/inspect-facturapi-invoice.ts b/apps/api/scripts/inspect-facturapi-invoice.ts new file mode 100644 index 0000000..d9a6ac9 --- /dev/null +++ b/apps/api/scripts/inspect-facturapi-invoice.ts @@ -0,0 +1,66 @@ +/** + * Inspect the shape of the response from Facturapi invoices.retrieve + * for a recent emission, to know what fields are actually populated. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { env } from '../src/config/env.js'; + +const CONTRIB_ID = '414b22a8-c6e2-4f39-be0f-7537a848107e'; +const TENANT_RFC = 'DESPACHO_MO3NI6U8_B9VGG'; +const INVOICE_ID = '69ebc61f87f122486514c3b4'; // latest + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: TENANT_RFC }, + select: { id: true, databaseName: true }, + }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + // Fetch org API key + const { rows } = await pool.query<{ facturapi_org_id: string }>( + `SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id=$1 AND active=true`, + [CONTRIB_ID], + ); + if (rows.length === 0) { + console.log('No facturapi_org_id found'); + return; + } + const orgId = rows[0].facturapi_org_id; + + // Get the org's API key (HTTP direct because SDK has issues) + const userKey = env.FACTURAPI_USER_KEY; + const keyRes = await fetch(`https://www.facturapi.io/v2/organizations/${orgId}/apikeys/test`, { + headers: { Authorization: `Bearer ${userKey}` }, + }); + const keyData = await keyRes.json(); + const apiKey = typeof keyData === 'string' ? keyData : keyData.apikey || keyData.key; + + // Retrieve the invoice + const invRes = await fetch(`https://www.facturapi.io/v2/invoices/${INVOICE_ID}`, { + headers: { Authorization: `Bearer ${apiKey}` }, + }); + const invoice = await invRes.json(); + + console.log('=== FACTURAPI INVOICE RESPONSE ==='); + console.log('Top-level keys:', Object.keys(invoice).sort().join(', ')); + console.log(''); + console.log('invoice.id =', invoice.id); + console.log('invoice.uuid =', invoice.uuid); + console.log('invoice.date =', invoice.date); + console.log('invoice.subtotal =', invoice.subtotal); + console.log('invoice.total =', invoice.total); + console.log('invoice.series =', invoice.series); + console.log('invoice.folio_number =', invoice.folio_number); + console.log('invoice.issuer =', JSON.stringify(invoice.issuer, null, 2)); + console.log('invoice.issuer_info =', JSON.stringify(invoice.issuer_info, null, 2)); + console.log('invoice.issuer_type =', invoice.issuer_type); + console.log('invoice.organization =', JSON.stringify(invoice.organization, null, 2)); + console.log('invoice.customer =', JSON.stringify(invoice.customer, null, 2)); + console.log('invoice.taxes =', JSON.stringify(invoice.taxes, null, 2)); + console.log('invoice.items =', JSON.stringify(invoice.items?.slice(0, 2), null, 2)); + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/inspect-latest-facturapi.ts b/apps/api/scripts/inspect-latest-facturapi.ts new file mode 100644 index 0000000..488eab3 --- /dev/null +++ b/apps/api/scripts/inspect-latest-facturapi.ts @@ -0,0 +1,41 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const TENANT_RFC = 'DESPACHO_MO3NI6U8_B9VGG'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: TENANT_RFC }, + select: { id: true, databaseName: true }, + }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + // Get the full latest Facturapi CFDI with ALL fields + const { rows } = await pool.query( + `SELECT * FROM cfdis + WHERE source = 'facturapi' + ORDER BY fecha_emision DESC + LIMIT 1`, + ); + if (rows.length === 0) { + console.log('No hay CFDIs Facturapi'); + return; + } + + const r = rows[0]; + console.log('UUID:', r.uuid); + console.log(''); + console.log('Campos relevantes de emisor/receptor:'); + const keys = Object.keys(r).sort(); + for (const k of keys) { + if (/emisor|receptor|regimen|contribuyente|type|tipo|facturapi|uso_cfdi|forma|metodo|total|iva|lugar|fecha|status|version|uuid|id|source|serie|folio|xml_original/i.test(k)) { + const v = r[k]; + const val = typeof v === 'string' && v.length > 200 ? v.substring(0, 200) + '…' : v; + console.log(` ${k} = ${val instanceof Date ? val.toISOString() : String(val).substring(0, 200)}`); + } + } + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/inspect-pair.ts b/apps/api/scripts/inspect-pair.ts new file mode 100644 index 0000000..11ffa82 --- /dev/null +++ b/apps/api/scripts/inspect-pair.ts @@ -0,0 +1,52 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const I_UUID = '5c874749-748f-11f0-96b1-2b9310891836'; +const E_UUID = '7163da3b-748f-11f0-9853-e97a8e1dedd9'; + +async function main() { + const tenants = await prisma.tenant.findMany({ select: { id: true, rfc: true, databaseName: true } }); + + for (const t of tenants) { + let pool; + try { pool = await tenantDb.getPool(t.id, t.databaseName); } catch { continue; } + + const { rows } = await pool.query( + `SELECT uuid, tipo_comprobante, metodo_pago, cfdi_tipo_relacion, cfdis_relacionados, + status, fecha_emision, total_mxn, iva_traslado_mxn, + rfc_emisor, rfc_receptor, contribuyente_id, type + FROM cfdis WHERE LOWER(uuid) IN (LOWER($1), LOWER($2))`, + [I_UUID, E_UUID], + ); + + if (rows.length === 0) continue; + console.log(`\n=== ${t.rfc} ===`); + for (const r of rows) { + const fe = new Date(r.fecha_emision).toISOString().slice(0, 10); + console.log(`\n UUID: ${r.uuid}`); + console.log(` tipo: ${r.tipo_comprobante}/${r.metodo_pago || '?'} rel=${r.cfdi_tipo_relacion ?? 'null'} status=${r.status} type=${r.type}`); + console.log(` fecha: ${fe} total=${r.total_mxn} IVA=${r.iva_traslado_mxn}`); + console.log(` ${r.rfc_emisor} → ${r.rfc_receptor} contrib_id=${r.contribuyente_id}`); + console.log(` cfdis_relacionados: ${r.cfdis_relacionados ?? 'NULL'}`); + } + + // Si están ambos, verificar match de cfdis_relacionados + if (rows.length === 2) { + const i = rows.find((x: any) => x.uuid.toLowerCase() === I_UUID.toLowerCase()); + const e = rows.find((x: any) => x.uuid.toLowerCase() === E_UUID.toLowerCase()); + if (i && e) { + const iRels = (i.cfdis_relacionados || '').split('|').map((u: string) => u.trim().toLowerCase()).filter(Boolean); + const eRels = (e.cfdis_relacionados || '').split('|').map((u: string) => u.trim().toLowerCase()).filter(Boolean); + const overlap = iRels.filter((u: string) => eRels.includes(u)); + console.log(`\n I refs (${iRels.length}): ${iRels.join(', ').substring(0, 200)}`); + console.log(` E refs (${eRels.length}): ${eRels.join(', ').substring(0, 200)}`); + console.log(` Overlap (${overlap.length}): ${overlap.join(', ')}`); + + // Cruz: ¿la E referencia a la I directamente, o viceversa? + if (eRels.includes(I_UUID.toLowerCase())) console.log(` → E.cfdis_relacionados INCLUYE el UUID de I/07 PPD`); + if (iRels.includes(E_UUID.toLowerCase())) console.log(` → I.cfdis_relacionados INCLUYE el UUID de E`); + } + } + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/inspect-rfc.ts b/apps/api/scripts/inspect-rfc.ts new file mode 100644 index 0000000..8ba5738 --- /dev/null +++ b/apps/api/scripts/inspect-rfc.ts @@ -0,0 +1,48 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +const rawRfc = process.argv[2]; +if (!rawRfc) { + console.error('Usage: tsx scripts/inspect-rfc.ts '); + process.exit(1); +} +const rfc = rawRfc.toUpperCase(); + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + + const { rows: contrib } = await pool.query( + `SELECT * FROM contribuyentes WHERE UPPER(rfc) = $1`, + [rfc], + ); + if (contrib.length > 0) { + console.log(`\n[${t.rfc}] Contribuyente ${rfc}:`); + console.log(contrib[0]); + } + + const { rows: rfcEntry } = await pool.query( + `SELECT id, rfc, razon_social, regimen_fiscal, codigo_postal FROM rfcs WHERE UPPER(rfc) = $1`, + [rfc], + ); + if (rfcEntry.length > 0) { + console.log(`[${t.rfc}] rfcs table:`, rfcEntry[0]); + } + + if (contrib.length > 0) { + const { rows: org } = await pool.query( + `SELECT facturapi_org_id, csd_uploaded, active FROM facturapi_orgs WHERE contribuyente_id = $1`, + [contrib[0].entidad_id], + ); + if (org.length > 0) console.log(`[${t.rfc}] facturapi_orgs:`, org[0]); + } + } + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/invalidate-metricas-all.ts b/apps/api/scripts/invalidate-metricas-all.ts new file mode 100644 index 0000000..e4acf13 --- /dev/null +++ b/apps/api/scripts/invalidate-metricas-all.ts @@ -0,0 +1,159 @@ +/** + * Invalida TODAS las entradas en `metricas_mensuales` — marca para recompute + * cada (contribuyente_id, anio, mes) que tenga datos cacheados. Diseñado para + * usarse después de un cambio de fórmula que afecta resultados históricos + * (ej. 2026-04-23: NC tipo E con TipoRelacion=07 dejan de restar en Grupo 1). + * + * El cron `metricas-invalidations.job` (cada 15min) procesa el backlog. + * Para acelerar: `pnpm --filter @horux/api exec tsx -e "import { runProcessInvalidations } from './src/jobs/metricas-invalidations.job.js'; runProcessInvalidations().then(()=>process.exit(0))"` + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/invalidate-metricas-all.ts # ejecuta + * pnpm --filter @horux/api exec tsx scripts/invalidate-metricas-all.ts --dry # reporta sin escribir + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run'); +const REASON = process.argv.find(a => a.startsWith('--reason='))?.slice(9) || 'FORMULA_CHANGE_E07_GRUPO1'; + +interface PerTenantResult { + tenantId: string; + rfc: string; + databaseName: string; + metricasRows: number; + marcadasNuevas: number; + marcadasUpdate: number; + error?: string; +} + +async function invalidateTenant( + tenantId: string, + rfc: string, + databaseName: string, +): Promise { + const result: PerTenantResult = { + tenantId, + rfc, + databaseName, + metricasRows: 0, + marcadasNuevas: 0, + marcadasUpdate: 0, + }; + + const pool = await tenantDb.getPool(tenantId, databaseName); + + // Cuenta filas existentes en metricas_mensuales para reportar + const { rows: cnt } = await pool.query<{ n: number }>( + `SELECT COUNT(DISTINCT (contribuyente_id, anio, mes))::int AS n FROM metricas_mensuales`, + ); + result.metricasRows = cnt[0]?.n || 0; + if (result.metricasRows === 0) return result; + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + // Insert-or-update: si ya estaba marcada, sobrescribe reason y marcado_at + // para que el cron la re-procese con el motivo correcto. + const { rows: inserted } = await client.query<{ + contribuyente_id: string; + anio: number; + mes: number; + was_new: boolean; + }>( + ` + INSERT INTO metricas_invalidaciones (contribuyente_id, anio, mes, reason) + SELECT DISTINCT contribuyente_id, anio, mes, $1 AS reason + FROM metricas_mensuales + ON CONFLICT (contribuyente_id, anio, mes) DO UPDATE + SET reason = EXCLUDED.reason, marcado_at = now() + RETURNING contribuyente_id, anio, mes, (xmax = 0) AS was_new + `, + [REASON], + ); + + result.marcadasNuevas = inserted.filter(r => r.was_new).length; + result.marcadasUpdate = inserted.length - result.marcadasNuevas; + + if (DRY_RUN) { + await client.query('ROLLBACK'); + } else { + await client.query('COMMIT'); + } + } catch (err: any) { + await client.query('ROLLBACK').catch(() => {}); + result.error = err?.message || String(err); + } finally { + client.release(); + } + + return result; +} + +async function main() { + console.log(`=== Invalidate metricas_mensuales ${DRY_RUN ? '(DRY RUN — no writes)' : ''} ===`); + console.log(`Reason: ${REASON}\n`); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + orderBy: { rfc: 'asc' }, + }); + + console.log(`Tenants activos: ${tenants.length}\n`); + + const results: PerTenantResult[] = []; + for (const t of tenants) { + process.stdout.write(`[${t.rfc}] (${t.databaseName}) ... `); + try { + const r = await invalidateTenant(t.id, t.rfc, t.databaseName); + results.push(r); + if (r.error) { + console.log(`ERROR: ${r.error}`); + } else if (r.metricasRows === 0) { + console.log(`sin cache (skip)`); + } else { + console.log( + `cache=${r.metricasRows} (contrib,año,mes), marcadas=${r.marcadasNuevas + r.marcadasUpdate} (nuevas=${r.marcadasNuevas}, re-marcadas=${r.marcadasUpdate})`, + ); + } + } catch (err: any) { + console.log(`FATAL: ${err?.message || err}`); + results.push({ + tenantId: t.id, + rfc: t.rfc, + databaseName: t.databaseName, + metricasRows: 0, + marcadasNuevas: 0, + marcadasUpdate: 0, + error: err?.message || String(err), + }); + } + } + + const totalMetricas = results.reduce((s, r) => s + r.metricasRows, 0); + const totalMarcadas = results.reduce((s, r) => s + r.marcadasNuevas + r.marcadasUpdate, 0); + const tenantsTouched = results.filter(r => r.marcadasNuevas + r.marcadasUpdate > 0).length; + const tenantsFailed = results.filter(r => r.error).length; + + console.log(`\n=== Resumen ===`); + console.log(` Tenants procesados: ${results.length}`); + console.log(` Tenants con cache: ${tenantsTouched}`); + console.log(` Filas cache total: ${totalMetricas}`); + console.log(` Invalidaciones: ${totalMarcadas}${DRY_RUN ? ' (rolled back)' : ''}`); + if (tenantsFailed > 0) console.log(` Tenants con error: ${tenantsFailed}`); + + if (!DRY_RUN && totalMarcadas > 0) { + console.log(`\nCron metricas-invalidations procesará el backlog en <=15 min.`); + console.log(`Para disparar manual: runProcessInvalidations() desde un tsx -e ad-hoc.`); + } + + await prisma.$disconnect(); + process.exit(tenantsFailed > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/list-contribuyentes.ts b/apps/api/scripts/list-contribuyentes.ts new file mode 100644 index 0000000..53c48f8 --- /dev/null +++ b/apps/api/scripts/list-contribuyentes.ts @@ -0,0 +1,26 @@ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenants = await prisma.tenant.findMany({ where: { active: true }, select: { id: true, rfc: true, databaseName: true } }); + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + // descubrir tablas con 'entidad' o 'contribuyente' en el nombre + const { rows: tbls } = await pool.query(`SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND (table_name LIKE '%entidad%' OR table_name LIKE '%contribuyente%') ORDER BY table_name`); + console.log(`\n[${t.rfc}] tablas:`, tbls.map((r: any) => r.table_name).join(', ')); + + // Join con rfcs si existe + try { + const { rows } = await pool.query( + `SELECT c.entidad_id, c.rfc, r.razon_social, c.regimen_fiscal, c.codigo_postal + FROM contribuyentes c + LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc) + ORDER BY r.razon_social NULLS LAST, c.rfc`, + ); + for (const r of rows) console.log(' ', r); + } catch (e: any) { + console.log(' ERR:', e.message); + } + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/migrate-tenants.ts b/apps/api/scripts/migrate-tenants.ts new file mode 100644 index 0000000..9a10f19 --- /dev/null +++ b/apps/api/scripts/migrate-tenants.ts @@ -0,0 +1,33 @@ +/** + * Eager tenant migration script. + * Run: pnpm --filter @horux/api db:migrate-tenants + * Or: pnpm db:migrate-tenants (from monorepo root via Turborepo) + * + * Applies pending SQL migrations to all active tenant databases. + */ +import { migrateAll } from '../src/config/tenant-migrations.js'; + +async function main() { + console.log('=== Tenant Schema Migration (Eager) ===\n'); + + const start = Date.now(); + const result = await migrateAll(); + const elapsed = ((Date.now() - start) / 1000).toFixed(1); + + console.log(`\n=== Done in ${elapsed}s ===`); + console.log(` Migrated: ${result.success}`); + console.log(` Up-to-date: ${result.skipped}`); + console.log(` Failed: ${result.failed}`); + + if (result.failed > 0) { + console.error('\nSome tenants failed migration. Check logs above.'); + process.exit(1); + } + + process.exit(0); +} + +main().catch((err) => { + console.error('Fatal error:', err); + process.exit(1); +}); diff --git a/apps/api/scripts/otf-ingresos.ts b/apps/api/scripts/otf-ingresos.ts new file mode 100644 index 0000000..03b980e --- /dev/null +++ b/apps/api/scripts/otf-ingresos.ts @@ -0,0 +1,27 @@ +process.env.METRICAS_BYPASS_CACHE = '1'; +import { prisma, tenantDb } from '../src/config/database.js'; +import { calcularIngresosPorRegimen } from '../src/services/dashboard.service.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'b3761db6-0b8d-4251-8078-4ddc31e9c75b'; +const yearMonth = process.argv[4] || '2025-05'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) return; + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const [anio, mes] = yearMonth.split('-').map(Number); + const lastDay = new Date(anio, mes, 0).getDate(); + const fi = `${yearMonth}-01`; + const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`; + + const r = await calcularIngresosPorRegimen(pool, tenant.id, fi, ff, undefined, undefined, false, contribuyenteId); + console.log(`\n=== Ingresos ${yearMonth} contrib=${contribuyenteId} (BYPASS_CACHE=1) ===`); + console.log(`Total: ${r.total.toFixed(2)}`); + for (const p of r.porRegimen) { + console.log(` ${p.regimenClave} (${p.regimenDescripcion}): ${p.monto.toFixed(2)}`); + } + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/preview-emails.mjs b/apps/api/scripts/preview-emails.mjs new file mode 100644 index 0000000..8a87023 --- /dev/null +++ b/apps/api/scripts/preview-emails.mjs @@ -0,0 +1,164 @@ +#!/usr/bin/env node +/** + * Genera los 8 templates de email como archivos HTML estáticos en + * `apps/api/email-previews/` para revisar el diseño en el navegador + * sin necesidad de SMTP configurado. + * + * Uso: + * pnpm email:preview + * + * Tras correr, abre `apps/api/email-previews/index.html` para ver + * el listado con links a cada template. + */ +import { writeFileSync, mkdirSync, rmSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = resolve(__dirname, '..'); +const OUT_DIR = resolve(ROOT, 'email-previews'); + +// Datos de ejemplo realistas para cada template +const SAMPLES = { + 'welcome.html': { + label: 'Bienvenida', + fixture: { nombre: 'Carlos Hernández', email: 'carlos@empresa.com', tempPassword: 'a3f2c891' }, + importPath: '../src/services/email/templates/welcome.ts', + fnName: 'welcomeEmail', + }, + 'password-reset.html': { + label: 'Recuperación de contraseña', + fixture: { nombre: 'Carlos Hernández', resetUrl: 'https://horuxfin.com/reset-password?token=a8e4f...' }, + importPath: '../src/services/email/templates/password-reset.ts', + fnName: 'passwordResetEmail', + }, + 'payment-confirmed.html': { + label: 'Pago confirmado', + fixture: { nombre: 'Carlos Hernández', amount: 780, plan: 'Business + IA', date: new Date().toLocaleDateString('es-MX') }, + importPath: '../src/services/email/templates/payment-confirmed.ts', + fnName: 'paymentConfirmedEmail', + }, + 'payment-failed.html': { + label: 'Pago rechazado', + fixture: { nombre: 'Carlos Hernández', amount: 780, plan: 'Business + IA' }, + importPath: '../src/services/email/templates/payment-failed.ts', + fnName: 'paymentFailedEmail', + }, + 'subscription-cancelled.html': { + label: 'Suscripción cancelada', + fixture: { nombre: 'Carlos Hernández', plan: 'Business + IA' }, + importPath: '../src/services/email/templates/subscription-cancelled.ts', + fnName: 'subscriptionCancelledEmail', + }, + 'subscription-expiring.html': { + label: 'Suscripción por vencer', + fixture: { nombre: 'Carlos Hernández', plan: 'Business + IA', expiresAt: '15 de mayo, 2026' }, + importPath: '../src/services/email/templates/subscription-expiring.ts', + fnName: 'subscriptionExpiringEmail', + }, + 'fiel-notification.html': { + label: 'e.firma cargada (admin)', + fixture: { clienteNombre: 'Empresa Demo SA de CV', clienteRfc: 'EDE123456AB1' }, + importPath: '../src/services/email/templates/fiel-notification.ts', + fnName: 'fielNotificationEmail', + }, + 'weekly-update.html': { + label: 'Actualización semanal', + fixture: { + nombre: 'Carlos Hernández', + empresa: 'Empresa Demo SA de CV', + periodoLabel: 'Abril 2026', + kpis: { + ingresos: 285430.50, + egresos: 142900.00, + utilidad: 142530.50, + margen: 49.9, + ivaBalance: 18420.00, + ivaAFavorAcumulado: 32100.00, + cfdisEmitidos: 47, + cfdisRecibidos: 23, + }, + alertas: [ + { titulo: 'Cliente en lista negra', mensaje: '1 cliente con situación SAT "Definitivo".', prioridad: 'alta' }, + { titulo: 'Concentración alta de proveedores', mensaje: 'IHH = 6,840. Más del 50% del gasto en 1 proveedor.', prioridad: 'media' }, + { titulo: 'Pago en efectivo', mensaje: '3 facturas recibidas con forma de pago "01-Efectivo" este mes.', prioridad: 'baja' }, + ], + discrepanciasPorMes: [ + { label: 'Abril 2026', count: 2 }, + { label: 'Marzo 2026', count: 5 }, + { label: 'Febrero 2026', count: 0 }, + { label: 'Enero 2026', count: 1 }, + ], + fechaGeneracion: new Date().toLocaleString('es-MX', { dateStyle: 'long', timeStyle: 'short' }), + }, + importPath: '../src/services/email/templates/weekly-update.ts', + fnName: 'weeklyUpdateEmail', + }, + 'new-client-admin.html': { + label: 'Nuevo cliente registrado (admin)', + fixture: { + clienteNombre: 'Empresa Demo SA de CV', + clienteRfc: 'EDE123456AB1', + adminEmail: 'admin@empresademo.com', + adminNombre: 'Carlos Hernández', + tempPassword: 'a3f2c891', + databaseName: 'horux_ede123456ab1', + plan: 'mi_empresa_plus', + }, + importPath: '../src/services/email/templates/new-client-admin.ts', + fnName: 'newClientAdminEmail', + }, +}; + +async function main() { + // Limpia output previo y recrea + try { rmSync(OUT_DIR, { recursive: true, force: true }); } catch {} + mkdirSync(OUT_DIR, { recursive: true }); + + const generated = []; + for (const [filename, sample] of Object.entries(SAMPLES)) { + const modPath = resolve(__dirname, sample.importPath); + const mod = await import(pathToFileURL(modPath).href); + const fn = mod[sample.fnName]; + if (typeof fn !== 'function') { + console.error(`[email:preview] FAIL: ${sample.fnName} no exportada en ${modPath}`); + continue; + } + const html = fn(sample.fixture); + const outPath = resolve(OUT_DIR, filename); + writeFileSync(outPath, html, 'utf8'); + generated.push({ filename, label: sample.label }); + console.log(`[email:preview] ✓ ${filename}`); + } + + // Index navegable + const indexHtml = ` +Email previews — Horux 360 + +

Email previews — Horux 360

+

Generados desde los templates en apps/api/src/services/email/templates/ con datos de ejemplo. Cada link abre el HTML renderizado tal como llegaría al inbox del cliente.

+
+

Si modificas un template, vuelve a correr pnpm email:preview para regenerar.

+`; + + writeFileSync(resolve(OUT_DIR, 'index.html'), indexHtml, 'utf8'); + console.log(`\n[email:preview] ${generated.length} templates generados.`); + console.log(`[email:preview] Abre: ${resolve(OUT_DIR, 'index.html')}`); +} + +main().catch(err => { + console.error('[email:preview] FAIL:', err); + process.exit(1); +}); diff --git a/apps/api/scripts/process-metricas-now.ts b/apps/api/scripts/process-metricas-now.ts new file mode 100644 index 0000000..280d752 --- /dev/null +++ b/apps/api/scripts/process-metricas-now.ts @@ -0,0 +1,32 @@ +/** + * Dispara manualmente el procesamiento de `metricas_invalidaciones` para todos + * los tenants. Útil tras un `invalidate-metricas-all.ts` para no esperar al + * cron (cada 15 min). + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/process-metricas-now.ts + */ +import { prisma } from '../src/config/database.js'; +import { processAllTenantsInvalidations } from '../src/services/metricas-compute.service.js'; + +async function main() { + console.log('=== Procesar metricas_invalidaciones (all tenants) ===\n'); + const start = Date.now(); + const r = await processAllTenantsInvalidations(); + const elapsed = ((Date.now() - start) / 1000).toFixed(1); + console.log( + `\nTenants revisados: ${r.tenantsRevisados}\n` + + `Invalidaciones procesadas: ${r.totalProcesadas}\n` + + `Filas metricas_mensuales escritas: ${r.totalFilasEscritas}\n` + + `Errores: ${r.totalErrores}\n` + + `Tiempo: ${elapsed}s`, + ); + await prisma.$disconnect(); + process.exit(r.totalErrores > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/refresh-metricas-cache.ts b/apps/api/scripts/refresh-metricas-cache.ts new file mode 100644 index 0000000..074ec36 --- /dev/null +++ b/apps/api/scripts/refresh-metricas-cache.ts @@ -0,0 +1,59 @@ +/** + * Limpia el cache `metricas_mensuales` de TODOS los tenants activos. + * + * Ejecutar después de cambios fiscales en las fórmulas de ingresos/deducciones + * (dashboard.service.ts) — los valores ya escritos en el cache reflejan la + * fórmula vieja y muestran datos incorrectos para meses pasados hasta ser + * recomputados. + * + * Estrategia: TRUNCATE (vía DELETE) por tenant. La próxima consulta a un + * período pasado cae al path on-the-fly y rehidrata el cache con la fórmula + * vigente. No bloquea uso normal — solo aumenta latencia de la primera lectura. + * + * Idempotente. Por-tenant try/catch para que un tenant que falla no tumbe el + * resto. + */ +import { prisma, tenantDb } from '../src/config/database.js'; + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, nombre: true, rfc: true, databaseName: true }, + orderBy: { rfc: 'asc' }, + }); + + console.log(`[Cache Refresh] Iterando ${tenants.length} tenant(s) activo(s)...\n`); + + let totalRows = 0; + let okTenants = 0; + let failedTenants = 0; + + for (const t of tenants) { + try { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const result = await pool.query('DELETE FROM metricas_mensuales'); + const rows = result.rowCount ?? 0; + totalRows += rows; + okTenants++; + console.log(`✓ ${t.rfc.padEnd(15)} ${t.nombre.padEnd(40)} → ${rows.toLocaleString('es-MX')} filas borradas`); + } catch (err: any) { + failedTenants++; + console.error(`✗ ${t.rfc.padEnd(15)} ${t.nombre.padEnd(40)} → ERROR: ${err.message || err}`); + } + } + + console.log(`\n[Cache Refresh] Completado:`); + console.log(` Tenants OK: ${okTenants}`); + console.log(` Tenants fallidos: ${failedTenants}`); + console.log(` Total filas: ${totalRows.toLocaleString('es-MX')}`); + console.log(`\nLa próxima consulta a un período en cache lo recomputará on-demand`); + console.log(`con las fórmulas vigentes en dashboard.service.ts.`); + + await prisma.$disconnect(); + process.exit(failedTenants > 0 ? 1 : 0); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/apps/api/scripts/set-horux-custom.ts b/apps/api/scripts/set-horux-custom.ts new file mode 100644 index 0000000..009fd88 --- /dev/null +++ b/apps/api/scripts/set-horux-custom.ts @@ -0,0 +1,100 @@ +/** + * Configura la suscripción del tenant Horux 360 (HTS240708LJA) como Plan Custom: + * - amount: $10 + * - currentPeriodEnd: hoy + 300 días + * - status: authorized + * + * Idempotente — actualiza la suscripción existente o crea una nueva si no hay. + * Resetea `lastReminderDay`/`lastReminderSentAt` para que el cron de avisos + * arranque limpio respecto al nuevo período. + */ +import { prisma } from '../src/config/database.js'; + +async function main() { + const RFC = 'HTS240708LJA'; + const AMOUNT = 10; + const DAYS_AHEAD = 7; + + const tenant = await prisma.tenant.findUnique({ where: { rfc: RFC } }); + if (!tenant) { + console.error(`Tenant ${RFC} no encontrado.`); + process.exit(1); + } + + const now = new Date(); + const periodEnd = new Date(now.getTime() + DAYS_AHEAD * 24 * 60 * 60 * 1000); + + const existing = await prisma.subscription.findFirst({ + where: { tenantId: tenant.id }, + orderBy: { createdAt: 'desc' }, + }); + + console.log('Tenant:', { id: tenant.id, nombre: tenant.nombre, plan: tenant.plan }); + console.log('Subscription previa:', existing ? { + id: existing.id, + plan: existing.plan, + status: existing.status, + amount: existing.amount.toString(), + currentPeriodEnd: existing.currentPeriodEnd, + } : null); + + let sub; + if (existing) { + sub = await prisma.subscription.update({ + where: { id: existing.id }, + data: { + plan: 'custom', + amount: AMOUNT, + status: 'authorized', + currentPeriodStart: now, + currentPeriodEnd: periodEnd, + // Limpiar pending/upgrade residuales del estado anterior. + pendingPlan: null, + pendingFrequency: null, + pendingEffectiveAt: null, + upgradePreferenceId: null, + upgradeTargetPlan: null, + upgradeTargetAmount: null, + // Reset del tracker de avisos — período nuevo, ningún bucket notificado. + lastReminderDay: null, + lastReminderSentAt: null, + }, + }); + } else { + sub = await prisma.subscription.create({ + data: { + tenantId: tenant.id, + plan: 'custom', + amount: AMOUNT, + status: 'authorized', + frequency: 'monthly', + currentPeriodStart: now, + currentPeriodEnd: periodEnd, + }, + }); + } + + // El tenant también tiene un campo `plan` propio — alinearlo con la sub. + if (tenant.plan !== 'custom') { + await prisma.tenant.update({ where: { id: tenant.id }, data: { plan: 'custom' } }); + console.log(`Tenant.plan actualizado: ${tenant.plan} → custom`); + } + + console.log('Subscription final:', { + id: sub.id, + plan: sub.plan, + status: sub.status, + amount: sub.amount.toString(), + currentPeriodStart: sub.currentPeriodStart, + currentPeriodEnd: sub.currentPeriodEnd, + }); + + console.log(`\n✓ Plan Custom activo. Próximo cobro: ${periodEnd.toLocaleDateString('es-MX', { dateStyle: 'long' })} ($${AMOUNT})`); + + await prisma.$disconnect(); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/apps/api/scripts/setup-despachos-db.ts b/apps/api/scripts/setup-despachos-db.ts new file mode 100644 index 0000000..3d5fd2a --- /dev/null +++ b/apps/api/scripts/setup-despachos-db.ts @@ -0,0 +1,71 @@ +import { PrismaClient } from '@prisma/client'; +import bcrypt from 'bcryptjs'; + +const prisma = new PrismaClient(); + +async function main() { + console.log('Setting up horux_despachos database...'); + + // Create admin user + const hash = await bcrypt.hash('Admin12345!', 12); + + const user = await prisma.user.upsert({ + where: { email: 'ivan@horuxfin.com' }, + update: {}, + create: { + email: 'ivan@horuxfin.com', + passwordHash: hash, + nombre: 'Ivan Admin', + }, + }); + console.log('✅ User created:', user.email); + + // Find or create tenant + let tenant = await prisma.tenant.findFirst(); + if (!tenant) { + tenant = await prisma.tenant.create({ + data: { + nombre: 'Despacho Demo', + rfc: 'DDE250101AAA', + plan: 'trial', + databaseName: 'horux_dde250101aaa', + verticalProfile: 'CONTABLE', + dbMode: 'MANAGED', + trialEndsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), + }, + }); + console.log('✅ Tenant created:', tenant.nombre); + } else { + console.log('✅ Tenant exists:', tenant.nombre); + } + + // Create membership + await prisma.tenantMembership.upsert({ + where: { userId_tenantId: { userId: user.id, tenantId: tenant.id } }, + update: {}, + create: { + userId: user.id, + tenantId: tenant.id, + rolId: 1, + isOwner: true, + }, + }); + console.log('✅ Membership created (owner)'); + + // Set lastTenantId + await prisma.user.update({ + where: { id: user.id }, + data: { lastTenantId: tenant.id }, + }); + + console.log('\n🎉 Setup complete!'); + console.log('Login: ivan@horuxfin.com / Admin12345!'); + console.log('Tenant:', tenant.nombre, `(${tenant.rfc})`); +} + +main() + .catch((e) => { + console.error('Setup failed:', e); + process.exit(1); + }) + .finally(() => prisma.$disconnect()); diff --git a/apps/api/scripts/sweep-stale-sat-jobs.ts b/apps/api/scripts/sweep-stale-sat-jobs.ts new file mode 100644 index 0000000..0d0e632 --- /dev/null +++ b/apps/api/scripts/sweep-stale-sat-jobs.ts @@ -0,0 +1,47 @@ +/** + * CLI wrapper del watchdog. La lógica vive en + * `src/services/sat/sweep-stale-jobs.service.ts` para que también se pueda + * correr desde un cron (`sat-sync.job.ts`) sin duplicar código. + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/sweep-stale-sat-jobs.ts # dry-run + * pnpm --filter @horux/api exec tsx scripts/sweep-stale-sat-jobs.ts --apply # ejecuta + * STALE_RUNNING_HOURS=2 pnpm --filter @horux/api exec tsx scripts/sweep-stale-sat-jobs.ts + */ +import { prisma } from '../src/config/database.js'; +import { sweepStaleSatJobs } from '../src/services/sat/sweep-stale-jobs.service.js'; + +async function main() { + const apply = process.argv.includes('--apply'); + const pendingHours = Number(process.env.STALE_PENDING_HOURS || 12); + const runningHours = Number(process.env.STALE_RUNNING_HOURS || 4); + const mode = apply ? 'APPLY' : 'DRY-RUN'; + console.log(`=== SAT stale-jobs watchdog [${mode}] ===`); + console.log(` pending: nextRetryAt < now − ${pendingHours}h`); + console.log(` running: startedAt < now − ${runningHours}h`); + console.log(); + + const result = await sweepStaleSatJobs({ apply, pendingHours, runningHours }); + + console.log(`Encontrados:`); + console.log(` pending stale: ${result.pendingFound}`); + console.log(` running stale: ${result.runningFound}`); + + for (const e of result.entries) { + console.log(` ─ ${e.id} tenant=${e.tenantId} kind=${e.kind} edad=${e.ageHours}h`); + } + + if (!apply) { + console.log(`\n[DRY-RUN] No se aplicaron cambios. Pasa --apply para marcar como failed.`); + } else { + console.log(`\nMarcados como failed: pending=${result.pendingMarked} running=${result.runningMarked}`); + } + + await prisma.$disconnect(); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/test-emails.ts b/apps/api/scripts/test-emails.ts new file mode 100644 index 0000000..908c055 --- /dev/null +++ b/apps/api/scripts/test-emails.ts @@ -0,0 +1,96 @@ +import { emailService } from '../src/services/email/email.service.js'; + +const recipients = ['ivan@horuxfin.com', 'carlos@horuxfin.com']; + +async function sendAllSamples() { + for (const to of recipients) { + console.log(`\n=== Enviando a ${to} ===`); + + // 1. Welcome + console.log('1/6 Bienvenida...'); + await emailService.sendWelcome(to, { + nombre: 'Ivan Alcaraz', + email: 'ivan@horuxfin.com', + tempPassword: 'TempPass123!', + }); + + // 2. FIEL notification (goes to ADMIN_EMAIL, but we override for test) + console.log('2/6 Notificación FIEL...'); + // Send directly since sendFielNotification goes to admin + const { fielNotificationEmail } = await import('../src/services/email/templates/fiel-notification.js'); + const { createTransport } = await import('nodemailer'); + const { env } = await import('../src/config/env.js'); + const transport = createTransport({ + host: env.SMTP_HOST, + port: parseInt(env.SMTP_PORT), + secure: false, + auth: { user: env.SMTP_USER, pass: env.SMTP_PASS }, + }); + const fielHtml = fielNotificationEmail({ + clienteNombre: 'Horux 360', + clienteRfc: 'CAS200101XXX', + }); + await transport.sendMail({ + from: env.SMTP_FROM, + to, + subject: '[Horux 360] subió su FIEL (MUESTRA)', + html: fielHtml, + }); + + // 3. Payment confirmed + console.log('3/6 Pago confirmado...'); + await emailService.sendPaymentConfirmed(to, { + nombre: 'Ivan Alcaraz', + amount: 1499, + plan: 'Enterprise', + date: '16 de marzo de 2026', + }); + + // 4. Payment failed + console.log('4/6 Pago fallido...'); + const { paymentFailedEmail } = await import('../src/services/email/templates/payment-failed.js'); + const failedHtml = paymentFailedEmail({ + nombre: 'Ivan Alcaraz', + amount: 1499, + plan: 'Enterprise', + }); + await transport.sendMail({ + from: env.SMTP_FROM, + to, + subject: 'Problema con tu pago - Horux360 (MUESTRA)', + html: failedHtml, + }); + + // 5. Subscription expiring + console.log('5/6 Suscripción por vencer...'); + await emailService.sendSubscriptionExpiring(to, { + nombre: 'Ivan Alcaraz', + plan: 'Enterprise', + expiresAt: '21 de marzo de 2026', + }); + + // 6. Subscription cancelled + console.log('6/6 Suscripción cancelada...'); + const { subscriptionCancelledEmail } = await import('../src/services/email/templates/subscription-cancelled.js'); + const cancelledHtml = subscriptionCancelledEmail({ + nombre: 'Ivan Alcaraz', + plan: 'Enterprise', + }); + await transport.sendMail({ + from: env.SMTP_FROM, + to, + subject: 'Suscripción cancelada - Horux360 (MUESTRA)', + html: cancelledHtml, + }); + + console.log(`Listo: 6 correos enviados a ${to}`); + } + + console.log('\n=== Todos los correos enviados ==='); + process.exit(0); +} + +sendAllSamples().catch((err) => { + console.error('Error:', err); + process.exit(1); +}); diff --git a/apps/api/scripts/validate-dashboard-impuestos.ts b/apps/api/scripts/validate-dashboard-impuestos.ts new file mode 100644 index 0000000..4c992d1 --- /dev/null +++ b/apps/api/scripts/validate-dashboard-impuestos.ts @@ -0,0 +1,97 @@ +/** + * Valida la alineación dashboard ≡ impuestos tras refactor de getResumenIva. + * Para 5 muestras aleatorias por contribuyente, compara: + * dashboard.calcularIvaBalancePorRegimen().total vs + * impuestos.getResumenIva().resultado + * + * Deben coincidir céntimo por céntimo (Resultado = Trasladado − Acreditable − Retenido, + * usando los mismos 6 buckets del dashboard). + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/validate-dashboard-impuestos.ts + * METRICAS_BYPASS_CACHE=1 pnpm --filter @horux/api exec tsx scripts/validate-dashboard-impuestos.ts + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import * as dashboard from '../src/services/dashboard.service.js'; +import { getResumenIva } from '../src/services/impuestos.service.js'; + +const TOL = 0.01; + +function cmp(a: number, b: number): boolean { return Math.abs(a - b) <= TOL; } +function fmt(n: number): string { + return n.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +} + +async function main() { + console.log('=== Validación dashboard.balance ≡ impuestos.resultado ==='); + console.log(` BYPASS_CACHE=${process.env.METRICAS_BYPASS_CACHE === '1' ? 'YES' : 'no'}\n`); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + + let total = 0; + let pass = 0; + let fail = 0; + + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows: contribs } = await pool.query<{ entidad_id: string; nombre: string }>( + `SELECT c.entidad_id, eg.nombre + FROM contribuyentes c + JOIN entidades_gestionadas eg ON eg.id = c.entidad_id + WHERE EXISTS (SELECT 1 FROM metricas_mensuales m WHERE m.contribuyente_id = c.entidad_id)`, + ); + if (contribs.length === 0) continue; + console.log(`[${t.rfc}] ${contribs.length} contribuyentes`); + + for (const c of contribs) { + const { rows: samples } = await pool.query<{ anio: number; mes: number }>( + `SELECT anio, mes FROM ( + SELECT DISTINCT anio, mes FROM metricas_mensuales WHERE contribuyente_id = $1 + ) t + ORDER BY random() LIMIT 5`, + [c.entidad_id], + ); + console.log(` ${c.nombre}:`); + + for (const s of samples) { + total++; + const fi = `${s.anio}-${String(s.mes).padStart(2, '0')}-01`; + const lastDay = new Date(s.anio, s.mes, 0).getDate(); + const ff = `${s.anio}-${String(s.mes).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + + const bal = await dashboard.calcularIvaBalancePorRegimen( + pool, t.id, fi, ff, [], undefined, false, c.entidad_id, + ); + const resumen = await getResumenIva(pool, fi, ff, t.id, false, c.entidad_id); + + const mesLabel = `${s.anio}-${String(s.mes).padStart(2, '0')}`; + if (cmp(bal.total, resumen.resultado)) { + pass++; + console.log(` ✓ ${mesLabel} balance=$${fmt(bal.total)} resultado=$${fmt(resumen.resultado)}`); + } else { + fail++; + const delta = bal.total - resumen.resultado; + console.log(` ✗ ${mesLabel} balance=$${fmt(bal.total)} resultado=$${fmt(resumen.resultado)} Δ=$${fmt(delta)}`); + console.log(` T=$${fmt(resumen.trasladado)} A=$${fmt(resumen.acreditable)} R=$${fmt(resumen.retenido)}`); + } + } + } + } + + console.log(`\n=== Resumen ===`); + console.log(` Muestras: ${total}`); + console.log(` PASS: ${pass}`); + console.log(` FAIL: ${fail}`); + + await prisma.$disconnect(); + process.exit(fail > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/scripts/validate-gastos.ts b/apps/api/scripts/validate-gastos.ts new file mode 100644 index 0000000..ecf8e64 --- /dev/null +++ b/apps/api/scripts/validate-gastos.ts @@ -0,0 +1,115 @@ +/** + * Compara Gastos del Dashboard vs Drill-down para un mes/contribuyente. + * Identifica discrepancias y rompe el detalle por lado (factura/pago/NC). + * + * Uso: tsx scripts/validate-gastos.ts + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { calcularEgresosPorRegimen } from '../src/services/dashboard.service.js'; + +const tenantRfcArg = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || 'd745a915-6a23-4818-944b-a7e1e18e536a'; +const yearMonth = process.argv[4] || '2025-02'; + +async function main() { + const tenant = await prisma.tenant.findFirst({ + where: { rfc: tenantRfcArg, active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + if (!tenant) { console.error('Tenant not found'); process.exit(1); } + + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + const [anio, mes] = yearMonth.split('-').map(Number); + const lastDay = new Date(anio, mes, 0).getDate(); + const fi = `${yearMonth}-01`; + const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`; + + console.log(`\n=== Contribuyente ${contribuyenteId} — ${fi} a ${ff} ===\n`); + + // 1. Dashboard (calcularEgresosPorRegimen) + const dashboard = await calcularEgresosPorRegimen( + pool, tenant.id, fi, ff, undefined, undefined, false, contribuyenteId, + ); + console.log('DASHBOARD calcularEgresosPorRegimen:'); + console.log(` total: ${dashboard.total.toFixed(2)}`); + for (const r of dashboard.porRegimen) { + console.log(` ${r.regimenClave} (${r.regimenDescripcion}): ${r.monto.toFixed(2)}`); + } + + // 2. Drill-down query (simulated — bucket=gastos uniforme) + const IMP_TRAS = `COALESCE(iva_traslado_mxn,0) + COALESCE(ieps_traslado_mxn,0) + COALESCE(impuestos_locales_trasladado_mxn,0)`; + const IMP_TRAS_PAGO = `COALESCE(iva_traslado_pago_mxn,0) + COALESCE(ieps_traslado_pago_mxn,0)`; + const EXCL_MONTO = `COALESCE((SELECT SUM(COALESCE(cc.importe_mxn,0) - COALESCE(cc.descuento_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ('84121603','93161608','85101501','85121800')), 0)`; + + // bucket=gastos: RECIBIDO I PUE + RECIBIDO P + RECIBIDO E PUE (excl 07) + // Sumamos tomando en cuenta el signo (E resta) + const { rows: drillRows } = await pool.query( + `SELECT + type, tipo_comprobante, metodo_pago, + COALESCE(cfdi_tipo_relacion, '') AS tipo_rel, + COUNT(*)::int AS n, + SUM(total_mxn) AS total_bruto, + SUM(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})) AS total_neto, + SUM(COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO})) AS pago_neto + FROM cfdis + WHERE ( + (type = 'RECIBIDO' AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND COALESCE(cfdi_tipo_relacion, '') <> '07') + OR (type = 'RECIBIDO' AND tipo_comprobante = 'P') + OR (type = 'RECIBIDO' AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND COALESCE(cfdi_tipo_relacion, '') <> '07') + ) + AND regimen_fiscal_receptor IN ('605','606','612','621','625','626','601','603','607','608','610','611','614','615','620','622','623','624') + AND status NOT IN ('Cancelado','0') + AND ((tipo_comprobante='P' AND fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')) + OR (tipo_comprobante!='P' AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day'))) + AND contribuyente_id = $3 + GROUP BY type, tipo_comprobante, metodo_pago, tipo_rel + ORDER BY tipo_comprobante, metodo_pago`, + [fi, ff, contribuyenteId], + ); + + console.log(`\nDRILL-DOWN bucket=gastos (filas del drill por bucket):`); + let drillSumaFacturas = 0, drillSumaPagos = 0, drillSumaNC = 0; + for (const r of drillRows) { + const tc = r.tipo_comprobante; + const valor = tc === 'P' ? Number(r.pago_neto) : Number(r.total_neto); + console.log(` ${r.type} ${tc} ${r.metodo_pago || '-'} rel=${r.tipo_rel || '-'} n=${r.n} total_bruto=${Number(r.total_bruto).toFixed(2)} valor_neto=${valor.toFixed(2)}`); + if (tc === 'I') drillSumaFacturas += valor; + else if (tc === 'P') drillSumaPagos += valor; + else if (tc === 'E') drillSumaNC += valor; + } + const drillTotal = drillSumaFacturas + drillSumaPagos - drillSumaNC; + console.log(` → facturas=${drillSumaFacturas.toFixed(2)} pagos=${drillSumaPagos.toFixed(2)} NC=${drillSumaNC.toFixed(2)}`); + console.log(` → drill total = ${drillTotal.toFixed(2)}`); + + // 3. Comparación + const delta = dashboard.total - drillTotal; + console.log(`\n=== COMPARATIVA ===`); + console.log(` Dashboard: ${dashboard.total.toFixed(2)}`); + console.log(` Drill-down: ${drillTotal.toFixed(2)}`); + console.log(` Delta: ${delta.toFixed(2)}`); + + if (Math.abs(delta) < 0.01) { + console.log(` ✓ CUADRAN`); + } else { + console.log(` ✗ NO CUADRAN — investigar`); + } + + // 4. Régimenes del receptor que aparecen vs los ignorados + const { rows: regsReceptor } = await pool.query( + `SELECT DISTINCT regimen_fiscal_receptor + FROM cfdis + WHERE contribuyente_id = $1 + AND type = 'RECIBIDO' + AND fecha_emision >= $2::date AND fecha_emision < ($3::date + interval '1 day') + ORDER BY regimen_fiscal_receptor`, + [contribuyenteId, fi, ff], + ); + console.log(`\nRegímenes en CFDIs RECIBIDOS del periodo:`, regsReceptor.map(r => r.regimen_fiscal_receptor).join(', ')); + + await prisma.$disconnect(); +} + +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/validate-ingresos.ts b/apps/api/scripts/validate-ingresos.ts new file mode 100644 index 0000000..c053b31 --- /dev/null +++ b/apps/api/scripts/validate-ingresos.ts @@ -0,0 +1,39 @@ +/** + * Paridad dashboard vs drill para INGRESOS de un contribuyente en un año. + * Similar a validate-gastos pero para el lado emisor. + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { calcularIngresosPorRegimen } from '../src/services/dashboard.service.js'; + +const tenantRfc = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG'; +const contribuyenteId = process.argv[3] || '414b22a8-c6e2-4f39-be0f-7537a848107e'; +const año = Number(process.argv[4] || '2025'); + +async function main() { + const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } }); + if (!tenant) { console.error('Tenant not found'); process.exit(1); } + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + + console.log(`\n=== Ingresos ${año} Contribuyente ${contribuyenteId} ===\n`); + console.log(`mes | total por régimen | total mes`); + + let totalAño = 0; + for (let m = 1; m <= 12; m++) { + const lastDay = new Date(año, m, 0).getDate(); + const mm = String(m).padStart(2, '0'); + const fi = `${año}-${mm}-01`; + const ff = `${año}-${mm}-${String(lastDay).padStart(2, '0')}`; + + const ingresos = await calcularIngresosPorRegimen( + pool, tenant.id, fi, ff, undefined, undefined, false, contribuyenteId, + ); + + const porReg = ingresos.porRegimen.map(r => `${r.regimenClave}:${r.monto.toFixed(2)}`).join(' / '); + console.log(`${mm} | ${porReg || '(sin datos)'} | ${ingresos.total.toFixed(2)}`); + totalAño += ingresos.total; + } + console.log(`\nTotal año: ${totalAño.toFixed(2)}`); + + await prisma.$disconnect(); +} +main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); }); diff --git a/apps/api/scripts/validate-metricas.ts b/apps/api/scripts/validate-metricas.ts new file mode 100644 index 0000000..2dd7e17 --- /dev/null +++ b/apps/api/scripts/validate-metricas.ts @@ -0,0 +1,160 @@ +/** + * Validación Tanda A: para cada contribuyente con datos en metricas_mensuales, + * toma 5 filas al azar y compara contra el cálculo on-the-fly usando los + * servicios canónicos (dashboard, impuestos). Reporta PASS/FAIL por celda. + * + * Uso: + * pnpm --filter @horux/api exec tsx scripts/validate-metricas.ts + */ +import { prisma, tenantDb } from '../src/config/database.js'; +import { + calcularIngresosPorRegimen, + calcularEgresosPorRegimen, +} from '../src/services/dashboard.service.js'; +import { getResumenIva } from '../src/services/impuestos.service.js'; + +const TOL = 0.01; // tolerancia de $0.01 para redondeo decimal + +interface StoredRow { + contribuyente_id: string; + anio: number; + mes: number; + regimen_fiscal: string | null; + ingresos_cobrados: string; + egresos_pagados: string; + iva_trasladado_total: string; + iva_acreditable: string; + iva_retenido_cobrado: string; + iva_resultado: string; + cfdis_emitidos_count: number; + cfdis_recibidos_count: number; + cfdis_cancelados_count: number; +} + +function cmp(a: number, b: number): boolean { + return Math.abs(a - b) <= TOL; +} + +function fmt(n: number): string { + return n.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +} + +async function validateRow( + tenantId: string, + row: StoredRow, +): Promise<{ pass: boolean; diffs: string[] }> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { databaseName: true }, + }); + if (!tenant) return { pass: false, diffs: ['tenant no encontrado'] }; + + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + const fi = `${row.anio}-${String(row.mes).padStart(2, '0')}-01`; + const lastDay = new Date(row.anio, row.mes, 0).getDate(); + const ff = `${row.anio}-${String(row.mes).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + + // Ejecutamos secuencial para evitar interferencia entre queries bajo el pool + // limit del tenant (max 3 conexiones). Con Promise.all concurrente, algunas + // queries compartidas de getResumenIva devolvían valores parciales. + const ingresos = await calcularIngresosPorRegimen(pool, tenantId, fi, ff, [], undefined, false, row.contribuyente_id); + const egresos = await calcularEgresosPorRegimen(pool, tenantId, fi, ff, [], undefined, false, row.contribuyente_id); + const resumenIva = await getResumenIva(pool, fi, ff, tenantId, false, row.contribuyente_id); + + const reg = row.regimen_fiscal; + const ingOtf = ingresos.porRegimen.find(r => r.regimenClave === reg)?.monto || 0; + const egrOtf = egresos.porRegimen.find(r => r.regimenClave === reg)?.monto || 0; + const trasOtf = resumenIva.trasladadoPorRegimen.find(r => r.regimenClave === reg)?.monto || 0; + const acrOtf = resumenIva.acreditablePorRegimen.find(r => r.regimenClave === reg)?.monto || 0; + const retOtf = resumenIva.retenidoPorRegimen.find(r => r.regimenClave === reg)?.monto || 0; + const resOtf = trasOtf - acrOtf - retOtf; + + const diffs: string[] = []; + const ingStored = Number(row.ingresos_cobrados); + const egrStored = Number(row.egresos_pagados); + const trasStored = Number(row.iva_trasladado_total); + const acrStored = Number(row.iva_acreditable); + const retStored = Number(row.iva_retenido_cobrado); + const resStored = Number(row.iva_resultado); + + if (!cmp(ingStored, ingOtf)) diffs.push(`ingresos: tabla=${fmt(ingStored)} vs otf=${fmt(ingOtf)}`); + if (!cmp(egrStored, egrOtf)) diffs.push(`egresos: tabla=${fmt(egrStored)} vs otf=${fmt(egrOtf)}`); + if (!cmp(trasStored, trasOtf)) diffs.push(`ivaTras: tabla=${fmt(trasStored)} vs otf=${fmt(trasOtf)}`); + if (!cmp(acrStored, acrOtf)) diffs.push(`ivaAcr: tabla=${fmt(acrStored)} vs otf=${fmt(acrOtf)}`); + if (!cmp(retStored, retOtf)) diffs.push(`ivaRet: tabla=${fmt(retStored)} vs otf=${fmt(retOtf)}`); + if (!cmp(resStored, resOtf)) diffs.push(`ivaResultado: tabla=${fmt(resStored)} vs otf=${fmt(resOtf)}`); + + return { pass: diffs.length === 0, diffs }; +} + +async function main() { + console.log('=== Validación metricas_mensuales (5 muestras aleatorias por contribuyente) ===\n'); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + + let totalMuestras = 0; + let totalPass = 0; + let totalFail = 0; + + for (const t of tenants) { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const { rows: contribs } = await pool.query<{ entidad_id: string; nombre: string }>( + `SELECT c.entidad_id, eg.nombre + FROM contribuyentes c + JOIN entidades_gestionadas eg ON eg.id = c.entidad_id + WHERE EXISTS ( + SELECT 1 FROM metricas_mensuales m WHERE m.contribuyente_id = c.entidad_id + )`, + ); + + if (contribs.length === 0) continue; + console.log(`\n[${t.rfc}] ${contribs.length} contribuyentes con datos`); + + for (const c of contribs) { + const { rows: samples } = await pool.query( + `SELECT contribuyente_id::text, anio, mes, regimen_fiscal, + ingresos_cobrados, egresos_pagados, + iva_trasladado_total, iva_acreditable, iva_retenido_cobrado, iva_resultado, + cfdis_emitidos_count, cfdis_recibidos_count, cfdis_cancelados_count + FROM metricas_mensuales + WHERE contribuyente_id = $1 + ORDER BY random() + LIMIT 5`, + [c.entidad_id], + ); + + console.log(` ${c.nombre} (${samples.length} muestras):`); + for (const s of samples) { + totalMuestras++; + const { pass, diffs } = await validateRow(t.id, s); + const mesLabel = `${s.anio}-${String(s.mes).padStart(2, '0')}`; + const reg = s.regimen_fiscal || 'null'; + if (pass) { + totalPass++; + console.log(` ✓ ${mesLabel} reg=${reg} ingresos=$${fmt(Number(s.ingresos_cobrados))}`); + } else { + totalFail++; + console.log(` ✗ ${mesLabel} reg=${reg} DIFFS:`); + for (const d of diffs) console.log(` - ${d}`); + } + } + } + } + + console.log(`\n=== Resumen ===`); + console.log(` Muestras totales: ${totalMuestras}`); + console.log(` PASS: ${totalPass}`); + console.log(` FAIL: ${totalFail}`); + + await prisma.$disconnect(); + process.exit(totalFail > 0 ? 1 : 0); +} + +main().catch(async (err) => { + console.error('Fatal:', err); + await prisma.$disconnect().catch(() => {}); + process.exit(1); +}); diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts new file mode 100644 index 0000000..6c5c3c9 --- /dev/null +++ b/apps/api/src/app.ts @@ -0,0 +1,112 @@ +import express, { type Express } from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import { env, getCorsOrigins } from './config/env.js'; +import { errorMiddleware } from './middlewares/error.middleware.js'; +import { authRoutes } from './routes/auth.routes.js'; +import { dashboardRoutes } from './routes/dashboard.routes.js'; +import { cfdiRoutes } from './routes/cfdi.routes.js'; +import { impuestosRoutes } from './routes/impuestos.routes.js'; +import { exportRoutes } from './routes/export.routes.js'; +import { alertasRoutes } from './routes/alertas.routes.js'; +import { notificationPreferencesRoutes } from './routes/notification-preferences.routes.js'; +import { tareasRoutes } from './routes/tareas.routes.js'; +import { papeleriaRoutes } from './routes/papeleria.routes.js'; +import { despachoStatsRoutes } from './routes/despacho-stats.routes.js'; +import { calendarioRoutes } from './routes/calendario.routes.js'; +import { reportesRoutes } from './routes/reportes.routes.js'; +import { usuariosRoutes } from './routes/usuarios.routes.js'; +import { tenantsRoutes } from './routes/tenants.routes.js'; +import fielRoutes from './routes/fiel.routes.js'; +import satRoutes from './routes/sat.routes.js'; +import { webhookRoutes } from './routes/webhook.routes.js'; +import { subscriptionRoutes } from './routes/subscription.routes.js'; +import { regimenRoutes } from './routes/regimen.routes.js'; +import { bancosRoutes } from './routes/bancos.routes.js'; +import { conciliacionRoutes } from './routes/conciliacion.routes.js'; +import { facturacionRoutes } from './routes/facturacion.routes.js'; +import { catalogosRoutes } from './routes/catalogos.routes.js'; +import { documentosRoutes } from './routes/documentos.routes.js'; +import { auditLogRoutes } from './routes/audit-log.routes.js'; +import { platformStaffRoutes } from './routes/platform-staff.routes.js'; +import despachoRoutes from './routes/despacho.routes.js'; +import contribuyenteRoutes from './routes/contribuyente.routes.js'; +import carteraRoutes from './routes/cartera.routes.js'; +import planCatalogoRoutes from './routes/plan-catalogo.routes.js'; +import connectorRoutes from './routes/connector.routes.js'; +import adminDashboardRoutes from './routes/admin-dashboard.routes.js'; +import adminImpersonateRoutes from './routes/admin-impersonate.routes.js'; +import adminClientesRoutes from './routes/admin-clientes.routes.js'; +import adminAddonsRoutes from './routes/admin-addons.routes.js'; +import despachoAuditRoutes from './routes/despacho-audit.routes.js'; +import metricasRoutes from './routes/metricas.routes.js'; + +const app: Express = express(); + +// Security. Helmet default incluye un CSP restrictivo que puede chocar con el +// frontend cuando éste embebe recursos propios (ej: /terminos embebe el PDF de +// /legal/). Dejamos CSP off en el API y centralizamos los headers de seguridad +// en next.config del web (X-Frame-Options, CSP frame-ancestors, HSTS, nosniff, +// Referrer-Policy) que es quien sirve la UI. El API solo responde JSON y +// archivos binarios (PDFs, XMLs) — no tiene contenido HTML que requiera CSP. +app.use(helmet({ + contentSecurityPolicy: false, + crossOriginResourcePolicy: { policy: 'cross-origin' }, // permite /legal/*.pdf embebido +})); +app.use(cors({ + origin: getCorsOrigins(), + credentials: true, +})); + +// Body parsing - 10MB default, bulk CFDI route has its own higher limit +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); + +// Health check +app.get('/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// API Routes +app.use('/api/auth', authRoutes); +app.use('/api/dashboard', dashboardRoutes); +app.use('/api/cfdi', cfdiRoutes); +app.use('/api/impuestos', impuestosRoutes); +app.use('/api/export', exportRoutes); +app.use('/api/alertas', alertasRoutes); +app.use('/api/notificaciones', notificationPreferencesRoutes); +app.use('/api/tareas', tareasRoutes); +app.use('/api/papeleria', papeleriaRoutes); +app.use('/api/despachos', despachoStatsRoutes); +app.use('/api/calendario', calendarioRoutes); +app.use('/api/reportes', reportesRoutes); +app.use('/api/usuarios', usuariosRoutes); +app.use('/api/tenants', tenantsRoutes); +app.use('/api/fiel', fielRoutes); +app.use('/api/sat', satRoutes); +app.use('/api/webhooks', webhookRoutes); +app.use('/api/subscriptions', subscriptionRoutes); +app.use('/api/regimenes', regimenRoutes); +app.use('/api/bancos', bancosRoutes); +app.use('/api/conciliacion', conciliacionRoutes); +app.use('/api/facturacion', facturacionRoutes); +app.use('/api/catalogos', catalogosRoutes); +app.use('/api/documentos', documentosRoutes); +app.use('/api/audit-log', auditLogRoutes); +app.use('/api/platform-staff', platformStaffRoutes); +app.use('/api/despachos', despachoRoutes); +app.use('/api/contribuyentes', contribuyenteRoutes); +app.use('/api/carteras', carteraRoutes); +app.use('/api/planes', planCatalogoRoutes); +app.use('/api/connector', connectorRoutes); +app.use('/api/admin/dashboard', adminDashboardRoutes); +app.use('/api/admin/impersonate', adminImpersonateRoutes); +app.use('/api/admin/clientes', adminClientesRoutes); +app.use('/api/admin/addons', adminAddonsRoutes); +app.use('/api/despacho/audit-log', despachoAuditRoutes); +app.use('/api/metricas', metricasRoutes); + +// Error handling +app.use(errorMiddleware); + +export { app }; diff --git a/apps/api/src/auth/passwords.ts b/apps/api/src/auth/passwords.ts new file mode 100644 index 0000000..2713c42 --- /dev/null +++ b/apps/api/src/auth/passwords.ts @@ -0,0 +1 @@ +export { hashPassword, verifyPassword } from '@horux/core'; diff --git a/apps/api/src/auth/tokens.ts b/apps/api/src/auth/tokens.ts new file mode 100644 index 0000000..9d951e4 --- /dev/null +++ b/apps/api/src/auth/tokens.ts @@ -0,0 +1,30 @@ +import { + generateAccessToken as coreGenerateAccessToken, + generateRefreshToken as coreGenerateRefreshToken, + verifyToken as coreVerifyToken, + decodeToken, + type TokenConfig, +} from '@horux/core'; +import type { JWTPayload } from '@horux/shared'; +import { env } from '../config/env.js'; + +const tokenConfig: TokenConfig = { + secret: env.JWT_SECRET, + accessExpiresIn: env.JWT_EXPIRES_IN, + refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN, +}; + +export function generateAccessToken(payload: Omit): string { + return coreGenerateAccessToken(payload, tokenConfig); +} + +export function generateRefreshToken(payload: Omit): string { + return coreGenerateRefreshToken(payload, tokenConfig); +} + +export function verifyToken(token: string): JWTPayload { + return coreVerifyToken(token, tokenConfig.secret); +} + +export { decodeToken }; +export type { JWTPayload }; diff --git a/apps/api/src/config/database.ts b/apps/api/src/config/database.ts new file mode 100644 index 0000000..3f6b162 --- /dev/null +++ b/apps/api/src/config/database.ts @@ -0,0 +1,234 @@ +import { PrismaClient } from '@prisma/client'; +import { Pool, type PoolConfig } from 'pg'; +import { env } from './env.js'; +import { migrate } from './tenant-migrations.js'; + +// =========================================== +// Prisma Client (central database: horux360) +// =========================================== + +declare global { + var prisma: PrismaClient | undefined; +} + +export const prisma = globalThis.prisma || new PrismaClient({ + log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], +}); + +if (process.env.NODE_ENV !== 'production') { + globalThis.prisma = prisma; +} + +// =========================================== +// TenantConnectionManager (per-tenant DBs) +// =========================================== + +interface PoolEntry { + pool: Pool; + lastAccess: Date; +} + +function parseDatabaseUrl(url: string) { + const parsed = new URL(url); + return { + host: parsed.hostname, + port: parseInt(parsed.port || '5432'), + user: decodeURIComponent(parsed.username), + password: decodeURIComponent(parsed.password), + }; +} + +class TenantConnectionManager { + private pools: Map = new Map(); + private cleanupInterval: NodeJS.Timeout | null = null; + private dbConfig: { host: string; port: number; user: string; password: string }; + private migratedPools: Set = new Set(); + + constructor() { + this.dbConfig = parseDatabaseUrl(env.DATABASE_URL); + this.cleanupInterval = setInterval(() => this.cleanupIdlePools(), 60_000); + } + + /** + * Get or create a connection pool for a tenant's database. + * Runs lazy migrations on first access (or after pool invalidation). + */ + async getPool( + tenantId: string, + databaseName: string, + connectionOverride?: { host: string; port: number; user: string; password: string }, + ): Promise { + let pool: Pool; + + const entry = this.pools.get(tenantId); + if (entry) { + entry.lastAccess = new Date(); + pool = entry.pool; + } else { + const poolConfig: PoolConfig = { + host: connectionOverride?.host ?? this.dbConfig.host, + port: connectionOverride?.port ?? this.dbConfig.port, + user: connectionOverride?.user ?? this.dbConfig.user, + password: connectionOverride?.password ?? this.dbConfig.password, + database: databaseName, + max: 3, + idleTimeoutMillis: 300_000, + connectionTimeoutMillis: 10_000, + }; + + pool = new Pool(poolConfig); + + pool.on('error', (err) => { + console.error(`[TenantDB] Pool error for tenant ${tenantId} (${databaseName}):`, err.message); + }); + + this.pools.set(tenantId, { pool, lastAccess: new Date() }); + } + + if (!this.migratedPools.has(tenantId)) { + try { + await migrate(pool, databaseName); + } catch (err) { + console.error(`[TenantDB] Migration error for tenant ${tenantId} (${databaseName}):`, err); + } + this.migratedPools.add(tenantId); + } + + return pool; + } + + /** + * Create a new database for a tenant with all required tables and indexes. + */ + async provisionDatabase(rfc: string, overrideDatabaseName?: string): Promise { + const databaseName = overrideDatabaseName || `horux_${rfc.toLowerCase().replace(/[^a-z0-9]/g, '')}`; + + const adminPool = new Pool({ + ...this.dbConfig, + database: 'postgres', + max: 1, + }); + + try { + const exists = await adminPool.query( + `SELECT 1 FROM pg_database WHERE datname = $1`, + [databaseName] + ); + + if (exists.rows.length > 0) { + throw new Error(`Database ${databaseName} already exists`); + } + + await adminPool.query(`CREATE DATABASE "${databaseName}"`); + + const tenantPool = new Pool({ + ...this.dbConfig, + database: databaseName, + max: 1, + }); + + try { + await migrate(tenantPool, databaseName); + } finally { + await tenantPool.end(); + } + + return databaseName; + } finally { + await adminPool.end(); + } + } + + /** + * Soft-delete: rename database so it can be recovered. + */ + async deprovisionDatabase(databaseName: string): Promise { + // Close any active pool for this tenant + for (const [tenantId, entry] of this.pools.entries()) { + // We check pool config to match the database + if ((entry.pool as any).options?.database === databaseName) { + await entry.pool.end().catch(() => {}); + this.pools.delete(tenantId); + } + } + + const timestamp = Date.now(); + const adminPool = new Pool({ + ...this.dbConfig, + database: 'postgres', + max: 1, + }); + + try { + await adminPool.query(` + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = $1 AND pid <> pg_backend_pid() + `, [databaseName]); + + await adminPool.query( + `ALTER DATABASE "${databaseName}" RENAME TO "${databaseName}_deleted_${timestamp}"` + ); + } finally { + await adminPool.end(); + } + } + + /** + * Invalidate (close and remove) a specific tenant's pool. + */ + invalidatePool(tenantId: string): void { + const entry = this.pools.get(tenantId); + if (entry) { + entry.pool.end().catch(() => {}); + this.pools.delete(tenantId); + } + this.migratedPools.delete(tenantId); + } + + /** + * Remove idle pools (not accessed in last 5 minutes). + */ + private cleanupIdlePools(): void { + const now = Date.now(); + const maxIdle = 5 * 60 * 1000; + + for (const [tenantId, entry] of this.pools.entries()) { + if (now - entry.lastAccess.getTime() > maxIdle) { + entry.pool.end().catch((err) => + console.error(`[TenantDB] Error closing idle pool for ${tenantId}:`, err.message) + ); + this.pools.delete(tenantId); + } + } + } + + /** + * Graceful shutdown: close all pools. + */ + async shutdown(): Promise { + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + } + + const closePromises = Array.from(this.pools.values()).map((entry) => + entry.pool.end() + ); + await Promise.all(closePromises); + this.pools.clear(); + } + + /** + * Get stats about active pools. + */ + getStats(): { activePools: number; tenantIds: string[] } { + return { + activePools: this.pools.size, + tenantIds: Array.from(this.pools.keys()), + }; + } + +} + +// Singleton instance +export const tenantDb = new TenantConnectionManager(); diff --git a/apps/api/src/config/env.ts b/apps/api/src/config/env.ts new file mode 100644 index 0000000..93a3a0a --- /dev/null +++ b/apps/api/src/config/env.ts @@ -0,0 +1,89 @@ +import { z } from 'zod'; +import { config } from 'dotenv'; +import { resolve } from 'path'; + +// Load .env file from the api package root +config({ path: resolve(process.cwd(), '.env') }); + +const envSchema = z.object({ + NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), + PORT: z.string().default('4000'), + DATABASE_URL: z.string(), + JWT_SECRET: z.string().min(32), + JWT_EXPIRES_IN: z.string().default('15m'), + JWT_REFRESH_EXPIRES_IN: z.string().default('7d'), + CORS_ORIGIN: z.string().default('http://localhost:3000'), + + // Frontend URL (for MercadoPago back_url, emails, etc.) + FRONTEND_URL: z.string().default('https://horuxfin.com'), + + // FIEL encryption (separate from JWT to allow independent rotation) + FIEL_ENCRYPTION_KEY: z.string().min(32), + FIEL_STORAGE_PATH: z.string().default('/var/horux/fiel'), + + // MercadoPago + MP_ACCESS_TOKEN: z.string().optional(), + // Token sandbox (TEST-...) para pruebas locales sin cobro real. Conviven con + // el de prod para no estar swap-eando manualmente. Solo se usa cuando + // MP_USE_SANDBOX=true. + MP_ACCESS_TOKEN_SANDBOX: z.string().optional(), + // Toggle global: cuando true, todas las llamadas a MP usan + // MP_ACCESS_TOKEN_SANDBOX. Default false → usa MP_ACCESS_TOKEN (prod). + MP_USE_SANDBOX: z.string().transform(v => v === 'true' || v === '1').default('false'), + MP_WEBHOOK_SECRET: z.string().optional(), + MP_NOTIFICATION_URL: z.string().optional(), + // Solo dev/staging: override del payer_email enviado a MercadoPago. Útil cuando + // el owner del tenant tiene el mismo email vinculado al MP_ACCESS_TOKEN + // (vendedor) — MP rechaza con "Payer and collector cannot be the same user". + // Al setearlo, todas las llamadas a MP usan este email como payer en lugar del + // owner real. Production: dejar vacío. (string vacío se trata como undefined + // para que prod pueda dejar la línea declarada sin valor sin romper Zod.) + MP_TEST_PAYER_EMAIL: z.preprocess( + v => (v === '' ? undefined : v), + z.string().email().optional(), + ), + + // SMTP (Gmail Workspace) + SMTP_HOST: z.string().default('smtp.gmail.com'), + SMTP_PORT: z.string().default('587'), + SMTP_USER: z.string().optional(), + SMTP_PASS: z.string().optional(), + SMTP_FROM: z.string().default('Horux360 '), + + // Admin notification email + ADMIN_EMAIL: z.string().default('carlos@horuxfin.com'), + + // Facturapi + FACTURAPI_USER_KEY: z.string().optional(), + + // Cloudflare Tunnel (connector BYO-DB) + CLOUDFLARE_API_TOKEN: z.string().optional(), + CLOUDFLARE_ACCOUNT_ID: z.string().optional(), + CLOUDFLARE_TUNNEL_DOMAIN: z.string().default('tunnel.horux.mx'), + + // KMS for encrypting DB connection strings and connector tokens + CONNECTOR_ENCRYPTION_KEY: z.string().optional(), + + // Metabase (auto-registro de BDs tenant en Metabase para BI) + METABASE_URL: z.string().optional(), + METABASE_USERNAME: z.string().optional(), + METABASE_PASSWORD: z.string().optional(), + METABASE_PG_HOST: z.string().optional(), + METABASE_PG_PORT: z.string().optional(), + METABASE_PG_USER: z.string().optional(), + METABASE_PG_PASSWORD: z.string().optional(), +}); + +const parsed = envSchema.safeParse(process.env); + +if (!parsed.success) { + console.error('❌ Invalid environment variables:', parsed.error.flatten().fieldErrors); + process.exit(1); +} + +export const env = parsed.data; + +// Parse CORS origins (comma-separated) into array +export function getCorsOrigins(): string[] { + return env.CORS_ORIGIN.split(',').map(origin => origin.trim()); +} diff --git a/apps/api/src/config/tenant-migrations.ts b/apps/api/src/config/tenant-migrations.ts new file mode 100644 index 0000000..df3cc43 --- /dev/null +++ b/apps/api/src/config/tenant-migrations.ts @@ -0,0 +1,143 @@ +import { Pool } from 'pg'; +import { readdir, readFile } from 'fs/promises'; +import { join } from 'path'; +import { prisma } from './database.js'; +import { env } from './env.js'; + +const MIGRATIONS_DIR = join(__dirname, '..', 'migrations', 'tenant'); + +export interface MigrationFile { + version: number; + name: string; + sql: string; +} + +export async function getMigrationFiles(): Promise { + let files: string[]; + + try { + files = await readdir(MIGRATIONS_DIR); + } catch (err: any) { + if (err.code === 'ENOENT') { + console.warn(`[Migrations] Directory not found: ${MIGRATIONS_DIR}`); + return []; + } + throw err; + } + + const pattern = /^(\d{3})_(.+)\.sql$/; + const migrations: MigrationFile[] = []; + + for (const file of files) { + const match = pattern.exec(file); + if (!match) continue; + + const version = parseInt(match[1], 10); + const name = file; + const sql = await readFile(join(MIGRATIONS_DIR, file), 'utf8'); + + migrations.push({ version, name, sql }); + } + + migrations.sort((a, b) => a.version - b.version); + return migrations; +} + +export async function migrate(pool: Pool, label?: string): Promise { + const prefix = label ? `[Migrations] (${label})` : '[Migrations]'; + + // Ensure schema_migrations table exists + await pool.query(` + CREATE TABLE IF NOT EXISTS schema_migrations ( + version INTEGER PRIMARY KEY, + name VARCHAR(255) NOT NULL, + applied_at TIMESTAMP DEFAULT NOW() + ); + `); + + // Get already-applied versions + const { rows } = await pool.query<{ version: number }>( + 'SELECT version FROM schema_migrations ORDER BY version' + ); + const appliedVersions = new Set(rows.map((r) => r.version)); + + // Get all migration files + const migrationFiles = await getMigrationFiles(); + const pending = migrationFiles.filter((m) => !appliedVersions.has(m.version)); + + if (pending.length === 0) { + return 0; + } + + console.log(`${prefix} Applying ${pending.length} pending migration(s)...`); + + for (const migration of pending) { + const client = await pool.connect(); + try { + await client.query('BEGIN'); + await client.query(migration.sql); + await client.query( + 'INSERT INTO schema_migrations (version, name) VALUES ($1, $2)', + [migration.version, migration.name] + ); + await client.query('COMMIT'); + console.log(`${prefix} Applied: ${migration.name}`); + } catch (err) { + await client.query('ROLLBACK'); + throw err; + } finally { + client.release(); + } + } + + return pending.length; +} + +export async function migrateAll(): Promise<{ + success: number; + failed: number; + skipped: number; +}> { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + + let success = 0; + let failed = 0; + let skipped = 0; + + for (const tenant of tenants) { + const parsed = new URL(env.DATABASE_URL); + const pool = new Pool({ + host: parsed.hostname, + port: parseInt(parsed.port || '5432'), + user: decodeURIComponent(parsed.username), + password: decodeURIComponent(parsed.password), + database: tenant.databaseName, + max: 1, + }); + + try { + const applied = await migrate(pool, tenant.rfc); + if (applied > 0) { + success++; + } else { + skipped++; + } + } catch (err: any) { + failed++; + console.error( + `[Migrations] (${tenant.rfc}) Failed: ${err.message}` + ); + } finally { + await pool.end(); + } + } + + console.log( + `[Migrations] Summary — success: ${success}, skipped: ${skipped}, failed: ${failed}` + ); + + return { success, failed, skipped }; +} diff --git a/apps/api/src/constants/obligaciones-fiscales.ts b/apps/api/src/constants/obligaciones-fiscales.ts new file mode 100644 index 0000000..6cb8662 --- /dev/null +++ b/apps/api/src/constants/obligaciones-fiscales.ts @@ -0,0 +1,84 @@ +export interface ObligacionFiscal { + id: string; + nombre: string; + fundamento: string; + frecuencia: 'mensual' | 'bimestral' | 'trimestral' | 'anual' | 'eventual'; + fechaLimite: string; + aplica: 'PM' | 'PF' | 'ambos'; + regimenes: string[] | null; // null = all regimes + condicion: string | null; + categoria: string; + recomendadaPorDefecto: boolean; +} + +export const OBLIGACIONES_CATALOGO: ObligacionFiscal[] = [ + // === FEDERALES MENSUALES (día 17) === + { id: 'isr-provisional', nombre: 'Pago provisional de ISR', fundamento: 'Art. 14 LISR', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'ambos', regimenes: null, condicion: null, categoria: 'Federal mensual', recomendadaPorDefecto: true }, + { id: 'iva-mensual', nombre: 'Pago mensual definitivo de IVA', fundamento: 'Art. 5-D LIVA', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'ambos', regimenes: null, condicion: null, categoria: 'Federal mensual', recomendadaPorDefecto: true }, + { id: 'ret-isr-sueldos', nombre: 'Retenciones de ISR por sueldos y salarios', fundamento: 'Art. 96 LISR', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'PM', regimenes: null, condicion: 'Facturas emitidas tipo N', categoria: 'Federal mensual', recomendadaPorDefecto: false }, + { id: 'ret-isr-asimilados', nombre: 'Retenciones de ISR por asimilados a salarios', fundamento: 'Art. 94 LISR', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'PM', regimenes: null, condicion: 'Facturas emitidas tipo N', categoria: 'Federal mensual', recomendadaPorDefecto: false }, + { id: 'ret-isr-honorarios', nombre: 'Retenciones de ISR por honorarios y arrendamiento a PF', fundamento: 'Art. 106/116 LISR', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'PM', regimenes: null, condicion: 'PM que contrate PF', categoria: 'Federal mensual', recomendadaPorDefecto: false }, + { id: 'ret-iva', nombre: 'Retenciones de IVA (servicios, fletes, outsourcing)', fundamento: 'Art. 1-A LIVA', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'PM', regimenes: null, condicion: 'Según supuesto', categoria: 'Federal mensual', recomendadaPorDefecto: false }, + { id: 'ieps', nombre: 'Pago definitivo de IEPS', fundamento: 'Art. 5 LIEPS', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'ambos', regimenes: null, condicion: 'Productores/importadores', categoria: 'Federal mensual', recomendadaPorDefecto: false }, + + // === INFORMATIVAS MENSUALES === + { id: 'diot', nombre: 'DIOT (Declaración Informativa de Operaciones con Terceros)', fundamento: 'Art. 32 LIVA', frecuencia: 'mensual', fechaLimite: 'Último día del mes siguiente', aplica: 'ambos', regimenes: null, condicion: 'PF con ingresos > $4M y todas las PM, excepto RESICO', categoria: 'Informativa mensual', recomendadaPorDefecto: false }, + { id: 'cont-balanza', nombre: 'Contabilidad Electrónica - Balanza de comprobación', fundamento: 'CFF Art. 28', frecuencia: 'mensual', fechaLimite: 'Día 3 del segundo mes siguiente', aplica: 'ambos', regimenes: null, condicion: 'PF con ingresos > $4M y todas las PM, excepto RESICO', categoria: 'Informativa mensual', recomendadaPorDefecto: false }, + { id: 'cont-catalogo', nombre: 'Contabilidad Electrónica - Catálogo de cuentas', fundamento: 'CFF Art. 28', frecuencia: 'eventual', fechaLimite: 'Cuando haya modificación', aplica: 'ambos', regimenes: null, condicion: 'PF con ingresos > $4M y todas las PM, excepto RESICO', categoria: 'Informativa mensual', recomendadaPorDefecto: false }, + + // === RESICO PM === + { id: 'isr-resico-pm', nombre: 'Pago provisional ISR RESICO-PM', fundamento: 'Art. 206 LISR', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'PM', regimenes: ['626'], condicion: null, categoria: 'RESICO PM', recomendadaPorDefecto: true }, + + // === RESICO PF === + { id: 'isr-resico-pf', nombre: 'Pago mensual ISR RESICO PF (1%-2.5%)', fundamento: 'Art. 113-E LISR', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'PF', regimenes: ['626'], condicion: null, categoria: 'RESICO PF', recomendadaPorDefecto: true }, + + // === ANUALES PM === + { id: 'anual-isr-pm', nombre: 'Declaración Anual de ISR PM', fundamento: 'Art. 76 LISR', frecuencia: 'anual', fechaLimite: '31 de marzo', aplica: 'PM', regimenes: null, condicion: null, categoria: 'Anual', recomendadaPorDefecto: true }, + { id: 'issif', nombre: 'ISSIF (Información sobre Situación Fiscal)', fundamento: 'CFF Art. 32-H', frecuencia: 'anual', fechaLimite: 'Con la declaración anual', aplica: 'PM', regimenes: null, condicion: null, categoria: 'Anual', recomendadaPorDefecto: false }, + { id: 'dictamen-fiscal', nombre: 'Dictamen Fiscal', fundamento: 'CFF Art. 32-A', frecuencia: 'anual', fechaLimite: '15 de mayo', aplica: 'PM', regimenes: null, condicion: 'Ingresos > $1,855M o grupos', categoria: 'Anual', recomendadaPorDefecto: false }, + { id: 'dim', nombre: 'DIM - Declaraciones Informativas Múltiples', fundamento: 'CFF', frecuencia: 'anual', fechaLimite: '15 de febrero', aplica: 'PM', regimenes: null, condicion: null, categoria: 'Anual', recomendadaPorDefecto: false }, + + // === ANUALES PF === + { id: 'anual-isr-pf', nombre: 'Declaración Anual PF', fundamento: 'Art. 150 LISR', frecuencia: 'anual', fechaLimite: '30 de abril', aplica: 'PF', regimenes: null, condicion: null, categoria: 'Anual', recomendadaPorDefecto: true }, + + // === SEGURIDAD SOCIAL === + { id: 'imss-cuotas', nombre: 'Cuotas obrero-patronales IMSS', fundamento: 'LSS', frecuencia: 'mensual', fechaLimite: 'Día 17 del mes siguiente', aplica: 'ambos', regimenes: null, condicion: 'Con empleados', categoria: 'Seguridad social', recomendadaPorDefecto: false }, + { id: 'infonavit', nombre: 'Aportaciones INFONAVIT + amortizaciones', fundamento: 'LINFONAVIT', frecuencia: 'bimestral', fechaLimite: 'Día 17 del mes siguiente al bimestre', aplica: 'ambos', regimenes: null, condicion: 'Con empleados', categoria: 'Seguridad social', recomendadaPorDefecto: false }, + { id: 'sar-retiro', nombre: 'SAR / Retiro', fundamento: 'LSS', frecuencia: 'bimestral', fechaLimite: 'Día 17 del mes siguiente al bimestre', aplica: 'ambos', regimenes: null, condicion: 'Con empleados', categoria: 'Seguridad social', recomendadaPorDefecto: false }, + { id: 'prima-riesgo', nombre: 'Determinación Prima de Riesgo de Trabajo', fundamento: 'LSS Art. 74', frecuencia: 'anual', fechaLimite: 'Febrero', aplica: 'ambos', regimenes: null, condicion: 'Con empleados', categoria: 'Seguridad social', recomendadaPorDefecto: false }, + + // === ESTATALES === + { id: 'isn', nombre: 'ISN - Impuesto Sobre Nómina', fundamento: 'Ley estatal', frecuencia: 'mensual', fechaLimite: 'Varía por estado (CDMX día 17)', aplica: 'ambos', regimenes: null, condicion: 'Con empleados', categoria: 'Estatal', recomendadaPorDefecto: false }, +]; + +/** + * Returns recommended obligations for a contribuyente based on: + * - PM vs PF (RFC length: 12 = PM, 13 = PF) + * - Specific regímenes + * - Whether they have nómina CFDIs (type N) + */ +export function getRecomendaciones(rfc: string, regimenes: string[], tieneNomina: boolean): ObligacionFiscal[] { + const esPM = rfc.length === 12; + const tipo = esPM ? 'PM' : 'PF'; + + return OBLIGACIONES_CATALOGO.filter(ob => { + // Filter by PM/PF + if (ob.aplica !== 'ambos' && ob.aplica !== tipo) return false; + + // Filter by régimen if specified + if (ob.regimenes && ob.regimenes.length > 0) { + if (!regimenes.some(r => ob.regimenes!.includes(r))) return false; + } + + // Always recommend IVA + ISR + if (ob.recomendadaPorDefecto) return true; + + // Recommend nómina obligations if they have type N + if (tieneNomina && ob.condicion?.includes('tipo N')) return true; + + // Recommend nómina-related social security if has employees + if (tieneNomina && ob.condicion?.includes('empleados')) return true; + + return false; + }); +} diff --git a/apps/api/src/controllers/activos-fijos.controller.ts b/apps/api/src/controllers/activos-fijos.controller.ts new file mode 100644 index 0000000..908d7bc --- /dev/null +++ b/apps/api/src/controllers/activos-fijos.controller.ts @@ -0,0 +1,87 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { AppError } from '../middlewares/error.middleware.js'; +import * as activosFijosService from '../services/activos-fijos.service.js'; + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +const listSchema = z.object({ + año: z.string().regex(/^\d{4}$/), + mes: z.string().regex(/^\d{1,2}$/), + contribuyenteId: z.string().uuid().optional(), + estado: z.enum(['todos', 'activos', 'baja', 'agotados']).optional(), +}); + +export async function list(req: Request, res: Response, next: NextFunction) { + try { + const q = listSchema.parse(req.query); + const data = await activosFijosService.listActivosFijos( + req.tenantPool!, + effectiveTenantId(req), + parseInt(q.año, 10), + parseInt(q.mes, 10), + q.contribuyenteId ?? null, + q.estado, + ); + res.json(data); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +const bajaSchema = z.object({ + fechaBaja: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), + motivo: z.enum(['venta', 'desecho', 'otro']), + comentario: z.string().max(2000).nullable().optional(), +}); + +export async function darDeBaja(req: Request, res: Response, next: NextFunction) { + try { + const cfdiId = parseInt(String(req.params.cfdiId), 10); + if (isNaN(cfdiId)) return next(new AppError(400, 'cfdiId inválido')); + const data = bajaSchema.parse(req.body); + await activosFijosService.darDeBaja( + req.tenantPool!, + cfdiId, + data.fechaBaja, + data.motivo, + req.user!.userId, + data.comentario ?? null, + ); + res.status(201).json({ ok: true }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +const usosExcluidosSchema = z.object({ + contribuyenteId: z.string().uuid(), + usos: z.array(z.string().regex(/^I0[1-8]$/)), +}); + +export async function setUsosExcluidos(req: Request, res: Response, next: NextFunction) { + try { + const { contribuyenteId, usos } = usosExcluidosSchema.parse(req.body); + const saved = await activosFijosService.setUsosExcluidos(req.tenantPool!, contribuyenteId, usos); + res.json({ usosExcluidos: saved }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function revertirBaja(req: Request, res: Response, next: NextFunction) { + try { + const cfdiId = parseInt(String(req.params.cfdiId), 10); + if (isNaN(cfdiId)) return next(new AppError(400, 'cfdiId inválido')); + const ok = await activosFijosService.revertirBaja(req.tenantPool!, cfdiId); + if (!ok) return next(new AppError(404, 'Activo no estaba dado de baja')); + res.status(204).send(); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/admin-addons.controller.ts b/apps/api/src/controllers/admin-addons.controller.ts new file mode 100644 index 0000000..0ca13ea --- /dev/null +++ b/apps/api/src/controllers/admin-addons.controller.ts @@ -0,0 +1,86 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { prisma } from '../config/database.js'; +import { isPlatformStaff } from '../utils/platform-admin.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { auditFromReq } from '../utils/audit.js'; + +async function requireStaff(req: Request) { + if (!req.user?.userId) throw new AppError(401, 'No autenticado'); + const isStaff = await isPlatformStaff(req.user.userId); + if (!isStaff) throw new AppError(403, 'Acceso restringido a staff de plataforma'); +} + +const updateSchema = z.object({ + nombre: z.string().min(1).max(200).optional(), + precio: z.number().nonnegative().optional(), + active: z.boolean().optional(), +}); + +/** Lista todo el catálogo de add-ons (incluye inactivos). */ +export async function listCatalogo(req: Request, res: Response, next: NextFunction) { + try { + await requireStaff(req); + const items = await prisma.planAddonCatalogo.findMany({ + orderBy: { codename: 'asc' }, + include: { + _count: { select: { subscriptionAddons: { where: { status: { in: ['authorized', 'pending'] } } } } }, + }, + }); + return res.json({ + data: items.map(i => ({ + id: i.id, + codename: i.codename, + nombre: i.nombre, + verticalProfile: i.verticalProfile, + precio: Number(i.precio), + frecuencia: i.frecuencia, + active: i.active, + delta: i.delta, + createdAt: i.createdAt.toISOString(), + suscripcionesActivas: i._count.subscriptionAddons, + })), + }); + } catch (err) { return next(err); } +} + +export async function updateCatalogoItem(req: Request, res: Response, next: NextFunction) { + try { + await requireStaff(req); + const id = String(req.params.id); + const data = updateSchema.parse(req.body); + const before = await prisma.planAddonCatalogo.findUnique({ where: { id } }); + if (!before) throw new AppError(404, 'Add-on no encontrado'); + + const updated = await prisma.planAddonCatalogo.update({ + where: { id }, + data: { + ...(data.nombre !== undefined ? { nombre: data.nombre } : {}), + ...(data.precio !== undefined ? { precio: data.precio } : {}), + ...(data.active !== undefined ? { active: data.active } : {}), + }, + }); + + auditFromReq(req, 'addon.catalogo_updated', { + entityType: 'PlanAddonCatalogo', + entityId: id, + metadata: { + codename: before.codename, + before: { nombre: before.nombre, precio: Number(before.precio), active: before.active }, + after: { nombre: updated.nombre, precio: Number(updated.precio), active: updated.active }, + }, + }); + + return res.json({ + id: updated.id, + codename: updated.codename, + nombre: updated.nombre, + precio: Number(updated.precio), + frecuencia: updated.frecuencia, + active: updated.active, + }); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} diff --git a/apps/api/src/controllers/admin-clientes.controller.ts b/apps/api/src/controllers/admin-clientes.controller.ts new file mode 100644 index 0000000..8b3baa2 --- /dev/null +++ b/apps/api/src/controllers/admin-clientes.controller.ts @@ -0,0 +1,46 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as svc from '../services/admin-clientes.service.js'; +import { isPlatformStaff } from '../utils/platform-admin.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +async function requireStaff(req: Request) { + if (!req.user?.userId) throw new AppError(401, 'No autenticado'); + const isStaff = await isPlatformStaff(req.user.userId); + if (!isStaff) throw new AppError(403, 'Acceso restringido a staff de plataforma'); +} + +/** + * Stats de gestión de clientes. + * + * Query params: + * - `from` (YYYY-MM-DD): inicio del rango. Default: primer día del mes en curso. + * - `to` (YYYY-MM-DD): fin del rango. Default: último día del mes en curso. + */ +export async function getStats(req: Request, res: Response, next: NextFunction) { + try { + await requireStaff(req); + const now = new Date(); + const defaultFrom = new Date(now.getFullYear(), now.getMonth(), 1); + const defaultTo = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999); + + const fromStr = String(req.query.from || '').trim(); + const toStr = String(req.query.to || '').trim(); + const from = fromStr ? new Date(fromStr + 'T00:00:00') : defaultFrom; + const to = toStr ? new Date(toStr + 'T23:59:59.999') : defaultTo; + if (isNaN(from.getTime()) || isNaN(to.getTime())) { + return next(new AppError(400, 'Rango de fechas inválido')); + } + const stats = await svc.getClientesStats({ from, to }); + return res.json(stats); + } catch (err) { return next(err); } +} + +export async function listUsuarios(req: Request, res: Response, next: NextFunction) { + try { + await requireStaff(req); + const tenantId = String(req.params.tenantId || ''); + if (!tenantId) return next(new AppError(400, 'tenantId requerido')); + const usuarios = await svc.getTenantUsuarios(tenantId); + return res.json({ data: usuarios }); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/admin-dashboard.controller.ts b/apps/api/src/controllers/admin-dashboard.controller.ts new file mode 100644 index 0000000..a7ea4bc --- /dev/null +++ b/apps/api/src/controllers/admin-dashboard.controller.ts @@ -0,0 +1,36 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as dashService from '../services/admin-dashboard.service.js'; +import { isPlatformStaff } from '../utils/platform-admin.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +async function requireStaff(req: Request) { + if (!req.user?.userId) throw new AppError(401, 'No autenticado'); + const isStaff = await isPlatformStaff(req.user.userId); + if (!isStaff) throw new AppError(403, 'Acceso restringido a staff de plataforma'); +} + +export async function getMetrics(req: Request, res: Response, next: NextFunction) { + try { + await requireStaff(req); + const metrics = await dashService.getDashboardMetrics(); + return res.json(metrics); + } catch (err) { return next(err); } +} + +export async function listDespachos(req: Request, res: Response, next: NextFunction) { + try { + await requireStaff(req); + const { vertical, status, search } = req.query as Record; + const despachos = await dashService.listAllDespachos({ vertical, status, search }); + return res.json({ data: despachos }); + } catch (err) { return next(err); } +} + +export async function getActivity(req: Request, res: Response, next: NextFunction) { + try { + await requireStaff(req); + const limit = Math.min(Number(req.query.limit) || 20, 100); + const activity = await dashService.getRecentActivity(limit); + return res.json({ data: activity }); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/admin-impersonate.controller.ts b/apps/api/src/controllers/admin-impersonate.controller.ts new file mode 100644 index 0000000..6469f7e --- /dev/null +++ b/apps/api/src/controllers/admin-impersonate.controller.ts @@ -0,0 +1,77 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { prisma } from '../config/database.js'; +import { hasPlatformRole } from '../utils/platform-admin.js'; +import { auditLog } from '../utils/audit.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +const impersonateSchema = z.object({ + despachoId: z.string().uuid('ID de despacho inválido'), + motivo: z.string().min(5, 'Motivo es obligatorio (mínimo 5 caracteres)'), +}); + +export async function startImpersonation(req: Request, res: Response, next: NextFunction) { + try { + if (!req.user?.userId) return next(new AppError(401, 'No autenticado')); + + const canImpersonate = await hasPlatformRole(req.user.userId, 'platform_admin') || + await hasPlatformRole(req.user.userId, 'platform_ti') || + await hasPlatformRole(req.user.userId, 'platform_support'); + if (!canImpersonate) return next(new AppError(403, 'No tienes permisos para impersonar')); + + const { despachoId, motivo } = impersonateSchema.parse(req.body); + + const tenant = await prisma.tenant.findUnique({ + where: { id: despachoId }, + select: { id: true, nombre: true, rfc: true, active: true }, + }); + if (!tenant) return next(new AppError(404, 'Despacho no encontrado')); + if (!tenant.active) return next(new AppError(403, 'Despacho inactivo')); + + await auditLog({ + userId: req.user.userId, + tenantId: despachoId, + action: 'admin.impersonate_start', + entityType: 'tenant', + entityId: despachoId, + metadata: { + motivo, + adminEmail: req.user.email, + despachoNombre: tenant.nombre, + despachoRfc: tenant.rfc, + ip: req.ip, + userAgent: req.headers['user-agent'], + }, + }); + + return res.json({ + despachoId: tenant.id, + nombre: tenant.nombre, + rfc: tenant.rfc, + message: 'Impersonación iniciada. Usa el header X-View-Tenant para acceder.', + }); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +export async function stopImpersonation(req: Request, res: Response, next: NextFunction) { + try { + if (!req.user?.userId) return next(new AppError(401, 'No autenticado')); + + const despachoId = req.body.despachoId as string | undefined; + + await auditLog({ + userId: req.user.userId, + tenantId: despachoId || undefined, + action: 'admin.impersonate_end', + metadata: { + adminEmail: req.user.email, + ip: req.ip, + }, + }); + + return res.json({ message: 'Impersonación finalizada' }); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/alertas.controller.ts b/apps/api/src/controllers/alertas.controller.ts new file mode 100644 index 0000000..6e058ab --- /dev/null +++ b/apps/api/src/controllers/alertas.controller.ts @@ -0,0 +1,506 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as alertasService from '../services/alertas.service.js'; +import { generarAlertasAutomaticas, SOSPECHOSA_TIPO_RELACION_WHERE_EXPORT } from '../services/alertas-auto.service.js'; +import { sincronizarAlertasManuales, getAlertasManualesPendientes, resolverAlerta } from '../services/alertas-manuales.service.js'; +import { getRegimenesActivosClavesEfectivos } from '../services/regimen.service.js'; +import { prisma } from '../config/database.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +const createAlertaSchema = z.object({ + tipo: z.enum(['vencimiento', 'discrepancia', 'iva_favor', 'declaracion', 'limite_cfdi', 'custom']), + titulo: z.string().min(1).max(200), + mensaje: z.string().min(1).max(2000), + prioridad: z.enum(['alta', 'media', 'baja']), + fechaVencimiento: z.string().optional(), +}); + +const updateAlertaSchema = z.object({ + leida: z.boolean().optional(), + resuelta: z.boolean().optional(), +}); + +export async function getAlertas(req: Request, res: Response, next: NextFunction) { + try { + const { leida, resuelta, prioridad } = req.query; + const alertas = await alertasService.getAlertas(req.tenantPool!, { + leida: leida === 'true' ? true : leida === 'false' ? false : undefined, + resuelta: resuelta === 'true' ? true : resuelta === 'false' ? false : undefined, + prioridad: prioridad as string, + }); + res.json(alertas); + } catch (error) { + next(error); + } +} + +export async function getAlerta(req: Request, res: Response, next: NextFunction) { + try { + const alerta = await alertasService.getAlertaById(req.tenantPool!, parseInt(String(req.params.id))); + if (!alerta) { + return res.status(404).json({ message: 'Alerta no encontrada' }); + } + res.json(alerta); + } catch (error) { + next(error); + } +} + +export async function createAlerta(req: Request, res: Response, next: NextFunction) { + try { + const data = createAlertaSchema.parse(req.body); + const alerta = await alertasService.createAlerta(req.tenantPool!, data); + res.status(201).json(alerta); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function updateAlerta(req: Request, res: Response, next: NextFunction) { + try { + const data = updateAlertaSchema.parse(req.body); + const alerta = await alertasService.updateAlerta(req.tenantPool!, parseInt(String(req.params.id)), data); + res.json(alerta); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function deleteAlerta(req: Request, res: Response, next: NextFunction) { + try { + await alertasService.deleteAlerta(req.tenantPool!, parseInt(String(req.params.id))); + res.status(204).send(); + } catch (error) { + next(error); + } +} + +export async function getStats(req: Request, res: Response, next: NextFunction) { + try { + const stats = await alertasService.getStats(req.tenantPool!); + res.json(stats); + } catch (error) { + next(error); + } +} + +export async function markAllAsRead(req: Request, res: Response, next: NextFunction) { + try { + await alertasService.markAllAsRead(req.tenantPool!); + res.json({ success: true }); + } catch (error) { + next(error); + } +} + +export async function getManualesPendientes(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + // Sincronizar primero (crear alertas para eventos vencidos nuevos) + await sincronizarAlertasManuales(req.tenantPool!, req.user!.tenantId, contribuyenteId || null); + // Devolver pendientes (filtered by contribuyente or user role) + const alertas = await getAlertasManualesPendientes( + req.tenantPool!, + contribuyenteId || null, + req.user!.userId, + req.user!.role, + ); + res.json(alertas); + } catch (error) { + next(error); + } +} + +export async function resolverAlertaManual(req: Request, res: Response, next: NextFunction) { + try { + await resolverAlerta(req.tenantPool!, String(req.params.id)); + res.json({ success: true }); + } catch (error) { + next(error); + } +} + +export async function getAlertasAutomaticas(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const alertas = await generarAlertasAutomaticas(req.tenantPool!, req.user!.tenantId, contribuyenteId || null); + res.json(alertas); + } catch (error) { + next(error); + } +} + +// Drill-down: Clientes en lista negra +export async function getListaNegraClientes(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const listaRfcs = await prisma.listaNegra.findMany({ + where: { situacion: { in: ['Definitivo', 'Presunto'] } }, + select: { rfc: true, nombre: true, situacion: true }, + }); + const rfcMap = new Map(listaRfcs.map(l => [l.rfc, l])); + + const { rows } = await req.tenantPool!.query(` + SELECT rfc_receptor as rfc, nombre_receptor as nombre, + COUNT(*)::int as cantidad, SUM(total_mxn) as total + FROM cfdis + WHERE type = 'EMITIDO' AND status NOT IN ('Cancelado', '0') AND tipo_comprobante = 'I' + ${cf} + GROUP BY rfc_receptor, nombre_receptor + ORDER BY total DESC + `); + + const result = rows + .filter((r: any) => rfcMap.has(r.rfc)) + .map((r: any) => ({ + rfc: r.rfc, + nombre: r.nombre, + cantidad: r.cantidad, + total: Number(r.total), + situacionSat: rfcMap.get(r.rfc)!.situacion, + })); + + res.json(result); + } catch (error) { + next(error); + } +} + +// Drill-down: Proveedores en lista negra +export async function getListaNegraProveedores(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const listaRfcs = await prisma.listaNegra.findMany({ + where: { situacion: { in: ['Definitivo', 'Presunto'] } }, + select: { rfc: true, nombre: true, situacion: true }, + }); + const rfcMap = new Map(listaRfcs.map(l => [l.rfc, l])); + + const { rows } = await req.tenantPool!.query(` + SELECT rfc_emisor as rfc, nombre_emisor as nombre, + COUNT(*)::int as cantidad, SUM(total_mxn) as total + FROM cfdis + WHERE type = 'RECIBIDO' AND status NOT IN ('Cancelado', '0') AND tipo_comprobante = 'I' + ${cf} + GROUP BY rfc_emisor, nombre_emisor + ORDER BY total DESC + `); + + const result = rows + .filter((r: any) => rfcMap.has(r.rfc)) + .map((r: any) => ({ + rfc: r.rfc, + nombre: r.nombre, + cantidad: r.cantidad, + total: Number(r.total), + situacionSat: rfcMap.get(r.rfc)!.situacion, + })); + + res.json(result); + } catch (error) { + next(error); + } +} + +// Drill-down: Concentración de clientes +export async function getConcentracionClientes(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const { rows } = await req.tenantPool!.query(` + SELECT rfc_receptor as rfc, nombre_receptor as nombre, + COUNT(*)::int as cantidad, + SUM(total_mxn) as total + FROM cfdis + WHERE type = 'EMITIDO' AND tipo_comprobante = 'I' + AND status NOT IN ('Cancelado', '0') AND total_mxn > 0 + ${cf} + GROUP BY rfc_receptor, nombre_receptor + ORDER BY total DESC + `); + + const totalGeneral = rows.reduce((s: number, r: any) => s + Number(r.total), 0); + const result = rows.map((r: any) => ({ + rfc: r.rfc, + nombre: r.nombre, + cantidad: r.cantidad, + total: Number(r.total), + participacion: totalGeneral > 0 ? Math.round((Number(r.total) / totalGeneral) * 10000) / 100 : 0, + })); + + res.json(result); + } catch (error) { + next(error); + } +} + +// Drill-down: Concentración de proveedores +export async function getConcentracionProveedores(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const { rows } = await req.tenantPool!.query(` + SELECT rfc_emisor as rfc, nombre_emisor as nombre, + COUNT(*)::int as cantidad, + SUM(total_mxn) as total + FROM cfdis + WHERE type = 'RECIBIDO' AND tipo_comprobante = 'I' + AND status NOT IN ('Cancelado', '0') AND total_mxn > 0 + ${cf} + GROUP BY rfc_emisor, nombre_emisor + ORDER BY total DESC + `); + + const totalGeneral = rows.reduce((s: number, r: any) => s + Number(r.total), 0); + const result = rows.map((r: any) => ({ + rfc: r.rfc, + nombre: r.nombre, + cantidad: r.cantidad, + total: Number(r.total), + participacion: totalGeneral > 0 ? Math.round((Number(r.total) / totalGeneral) * 10000) / 100 : 0, + })); + + res.json(result); + } catch (error) { + next(error); + } +} + +// Drill-down: CFDIs con discrepancia de régimen +export async function getDiscrepanciaRegimen(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const activos = await getRegimenesActivosClavesEfectivos(req.user!.tenantId, req.tenantPool!, contribuyenteId); + if (activos.length === 0) return res.json([]); + + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const { rows } = await req.tenantPool!.query(` + SELECT id, uuid, type, fecha_emision as "fechaEmision", + rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor", + rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor", + total_mxn as "totalMxn", regimen_fiscal_receptor as "regimenReceptor" + FROM cfdis + WHERE type = 'RECIBIDO' + AND status = 'Vigente' + AND fecha_cancelacion IS NULL + AND regimen_fiscal_receptor IS NOT NULL + AND regimen_fiscal_receptor != ALL($1) + AND id NOT IN (SELECT cfdi_id FROM cfdi_descartados WHERE tipo_alerta = 'discrepancia-regimen') + ${cf} + ORDER BY fecha_emision DESC + `, [activos]); + + res.json(rows); + } catch (error) { + next(error); + } +} + +// Drill-down: CFDIs cancelados +export async function getCancelados(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const hace5 = new Date(); + hace5.setFullYear(hace5.getFullYear() - 5); + + const { rows } = await req.tenantPool!.query(` + SELECT id, uuid, type, fecha_emision as "fechaEmision", + rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor", + rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor", + total_mxn as "totalMxn", fecha_cancelacion as "fechaCancelacion" + FROM cfdis + WHERE status IN ('Cancelado', '0') + AND fecha_emision >= $1::date + ${cf} + ORDER BY fecha_emision DESC + `, [hace5.toISOString().split('T')[0]]); + + res.json(rows); + } catch (error) { + next(error); + } +} + +// Drill-down: Facturas de periodos anteriores canceladas este mes +export async function getCancelacionesPeriodoAnterior(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const ahora = new Date(); + const inicioMes = `${ahora.getFullYear()}-${String(ahora.getMonth() + 1).padStart(2, '0')}-01`; + + const { rows } = await req.tenantPool!.query(` + SELECT id, uuid, type, fecha_emision as "fechaEmision", + rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor", + rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor", + total_mxn as "totalMxn", tipo_comprobante as "tipoComprobante", + fecha_cancelacion as "fechaCancelacion" + FROM cfdis + WHERE status IN ('Cancelado', '0') + AND fecha_cancelacion >= $1::date + AND fecha_emision < $1::date + ${cf} + ORDER BY fecha_cancelacion DESC + `, [inicioMes]); + + res.json(rows); + } catch (error) { + next(error); + } +} + +// Drill-down: CFDIs con pago en efectivo +export async function getEfectivo(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const { rows } = await req.tenantPool!.query(` + SELECT id, uuid, type, fecha_emision as "fechaEmision", + rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor", + rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor", + total_mxn as "totalMxn", forma_pago as "formaPago" + FROM cfdis + WHERE status NOT IN ('Cancelado', '0') AND tipo_comprobante = 'I' + AND forma_pago = '01' + ${cf} + ORDER BY fecha_emision DESC + `); + + res.json(rows); + } catch (error) { + next(error); + } +} + +// Drill-down: CFDIs tipo E con TipoRelacion sospechoso (debería ser 07) +export async function getTipoRelacionSospechosa(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND c.contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + const { rows } = await req.tenantPool!.query(` + SELECT c.id, c.uuid, c.type, c.fecha_emision AS "fechaEmision", + c.rfc_emisor AS "rfcEmisor", c.nombre_emisor AS "nombreEmisor", + c.rfc_receptor AS "rfcReceptor", c.nombre_receptor AS "nombreReceptor", + c.total_mxn AS "totalMxn", + c.tipo_comprobante AS "tipoComprobante", + c.cfdi_tipo_relacion AS "cfdiTipoRelacion", + c.cfdis_relacionados AS "cfdisRelacionados" + FROM cfdis c + WHERE ${SOSPECHOSA_TIPO_RELACION_WHERE_EXPORT} + ${cf} + ORDER BY c.fecha_emision DESC + `); + res.json(rows); + } catch (error) { + next(error); + } +} + +// ── Descarte de CFDIs de alertas ── + +export async function descartarCfdis(req: Request, res: Response, next: NextFunction) { + try { + const { cfdiIds, tipoAlerta } = z.object({ + cfdiIds: z.array(z.number().int()), + tipoAlerta: z.string().min(1), + }).parse(req.body); + + for (const cfdiId of cfdiIds) { + await req.tenantPool!.query( + `INSERT INTO cfdi_descartados (cfdi_id, tipo_alerta, descartado_por) + VALUES ($1, $2, $3) ON CONFLICT (cfdi_id, tipo_alerta) DO NOTHING`, + [cfdiId, tipoAlerta, req.user!.email], + ); + } + res.json({ descartados: cfdiIds.length }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function restaurarDescartados(req: Request, res: Response, next: NextFunction) { + try { + const { cfdiIds, tipoAlerta } = z.object({ + cfdiIds: z.array(z.number().int()).optional(), + tipoAlerta: z.string().min(1), + }).parse(req.body); + + if (cfdiIds && cfdiIds.length > 0) { + await req.tenantPool!.query( + `DELETE FROM cfdi_descartados WHERE tipo_alerta = $1 AND cfdi_id = ANY($2)`, + [tipoAlerta, cfdiIds], + ); + } else { + await req.tenantPool!.query( + `DELETE FROM cfdi_descartados WHERE tipo_alerta = $1`, + [tipoAlerta], + ); + } + res.json({ success: true }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function getDescartados(req: Request, res: Response, next: NextFunction) { + try { + const tipoAlerta = req.query.tipoAlerta as string; + if (!tipoAlerta) return next(new AppError(400, 'tipoAlerta requerido')); + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const cf = contribuyenteId + ? `AND c.contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'` + : ''; + + // JOIN con cfdis para devolver datos completos (mismo shape que el + // drill-down activo, para que el frontend pueda reutilizar el componente). + const { rows } = await req.tenantPool!.query(` + SELECT c.id, c.uuid, c.type, c.fecha_emision AS "fechaEmision", + c.rfc_emisor AS "rfcEmisor", c.nombre_emisor AS "nombreEmisor", + c.rfc_receptor AS "rfcReceptor", c.nombre_receptor AS "nombreReceptor", + c.total_mxn AS "totalMxn", + c.regimen_fiscal_receptor AS "regimenReceptor", + d.descartado_por AS "descartadoPor", + d.created_at AS "descartadoEn" + FROM cfdi_descartados d + JOIN cfdis c ON c.id = d.cfdi_id + WHERE d.tipo_alerta = $1 + ${cf} + ORDER BY d.created_at DESC + `, [tipoAlerta]); + res.json({ data: rows }); + } catch (error) { next(error); } +} diff --git a/apps/api/src/controllers/audit-log.controller.ts b/apps/api/src/controllers/audit-log.controller.ts new file mode 100644 index 0000000..fbc0efc --- /dev/null +++ b/apps/api/src/controllers/audit-log.controller.ts @@ -0,0 +1,87 @@ +import type { Request, Response, NextFunction } from 'express'; +import { prisma } from '../config/database.js'; +import { isGlobalAdmin } from '../utils/global-admin.js'; + +async function requireGlobalAdmin(req: Request, res: Response): Promise { + const isAdmin = await isGlobalAdmin(req.user!.tenantId, req.user!.role); + if (!isAdmin) { + res.status(403).json({ message: 'Solo el administrador global puede consultar el audit log' }); + } + return isAdmin; +} + +/** + * Lista eventos de audit con filtros opcionales. Admin global only. + * + * Query params: + * action — filtra por action prefix (ej: "subscription." matches todas las subs) + * tenantId — filtra a un tenant específico + * userId — filtra a un user específico + * from, to — rango de fechas (ISO) + * page, limit — paginación (default: 1, 50; max limit 200) + */ +export async function listAuditLog(req: Request, res: Response, next: NextFunction) { + try { + if (!(await requireGlobalAdmin(req, res))) return; + + const action = String(req.query.action || '').trim(); + const tenantId = String(req.query.tenantId || '').trim(); + const userId = String(req.query.userId || '').trim(); + const from = String(req.query.from || '').trim(); + const to = String(req.query.to || '').trim(); + const page = Math.max(1, parseInt(String(req.query.page || '1'), 10) || 1); + const limit = Math.min(200, Math.max(1, parseInt(String(req.query.limit || '50'), 10) || 50)); + + const where: any = {}; + if (action) where.action = { startsWith: action }; + if (tenantId) where.tenantId = tenantId; + if (userId) where.userId = userId; + if (from || to) { + where.createdAt = {}; + if (from) where.createdAt.gte = new Date(from); + if (to) where.createdAt.lte = new Date(to); + } + + const [total, rows] = await Promise.all([ + prisma.auditLog.count({ where }), + prisma.auditLog.findMany({ + where, + orderBy: { createdAt: 'desc' }, + skip: (page - 1) * limit, + take: limit, + }), + ]); + + // Enriquecer con user.email y tenant.nombre para display + const userIds = [...new Set(rows.map(r => r.userId).filter(Boolean))] as string[]; + const tenantIds = [...new Set(rows.map(r => r.tenantId).filter(Boolean))] as string[]; + + const [users, tenants] = await Promise.all([ + userIds.length + ? prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, email: true, nombre: true } }) + : [], + tenantIds.length + ? prisma.tenant.findMany({ where: { id: { in: tenantIds } }, select: { id: true, nombre: true, rfc: true } }) + : [], + ]); + + const userMap = new Map(users.map(u => [u.id, u])); + const tenantMap = new Map(tenants.map(t => [t.id, t])); + + const data = rows.map(r => ({ + ...r, + user: r.userId ? userMap.get(r.userId) || null : null, + tenant: r.tenantId ? tenantMap.get(r.tenantId) || null : null, + })); + + res.json({ + data, + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/auth.controller.ts b/apps/api/src/controllers/auth.controller.ts new file mode 100644 index 0000000..02029b2 --- /dev/null +++ b/apps/api/src/controllers/auth.controller.ts @@ -0,0 +1,203 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as authService from '../services/auth.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +const registerSchema = z.object({ + empresa: z.object({ + nombre: z.string().min(2, 'Nombre de empresa requerido'), + rfc: z.string().min(12).max(13, 'RFC inválido'), + }), + usuario: z.object({ + nombre: z.string().min(2, 'Nombre requerido'), + email: z.string().email('Email inválido'), + password: z.string().min(8, 'La contraseña debe tener al menos 8 caracteres'), + }), +}); + +const loginSchema = z.object({ + email: z.string().email('Email inválido'), + password: z.string().min(1, 'Contraseña requerida'), +}); + +export async function register(req: Request, res: Response, next: NextFunction) { + try { + const data = registerSchema.parse(req.body); + const result = await authService.register(data); + res.status(201).json(result); + } catch (error) { + if (error instanceof z.ZodError) { + return next(new AppError(400, error.errors[0].message)); + } + next(error); + } +} + +export async function login(req: Request, res: Response, next: NextFunction) { + try { + const data = loginSchema.parse(req.body); + const result = await authService.login(data); + res.json(result); + } catch (error) { + if (error instanceof z.ZodError) { + return next(new AppError(400, error.errors[0].message)); + } + next(error); + } +} + +export async function refresh(req: Request, res: Response, next: NextFunction) { + try { + const { refreshToken } = req.body; + if (!refreshToken) { + throw new AppError(400, 'Refresh token requerido'); + } + const tokens = await authService.refreshTokens(refreshToken); + res.json(tokens); + } catch (error) { + next(error); + } +} + +export async function logout(req: Request, res: Response, next: NextFunction) { + try { + const { refreshToken } = req.body; + if (refreshToken) { + await authService.logout(refreshToken); + } + res.json({ message: 'Sesión cerrada exitosamente' }); + } catch (error) { + next(error); + } +} + +export async function me(req: Request, res: Response, next: NextFunction) { + try { + res.json({ user: req.user }); + } catch (error) { + next(error); + } +} + +const passwordResetRequestSchema = z.object({ + email: z.string().email('Email inválido'), +}); + +const passwordResetConfirmSchema = z.object({ + token: z.string().min(10, 'Token inválido'), + newPassword: z.string().min(8, 'La contraseña debe tener al menos 8 caracteres'), +}); + +/** + * Solicita recuperación de contraseña. Responde 200 siempre (anti-enumeration), + * independiente de si el email existe o no. + */ +export async function requestPasswordReset(req: Request, res: Response, next: NextFunction) { + try { + const { email } = passwordResetRequestSchema.parse(req.body); + // Dispara async — no esperamos resultado para preservar timing constante + await authService.requestPasswordReset(email); + res.json({ + message: 'Si el email existe en nuestro sistema, recibirás un enlace para restablecer tu contraseña.', + }); + } catch (error) { + if (error instanceof z.ZodError) { + return next(new AppError(400, error.errors[0].message)); + } + next(error); + } +} + +/** + * Confirma recuperación con token + nueva contraseña. + */ +export async function confirmPasswordReset(req: Request, res: Response, next: NextFunction) { + try { + const { token, newPassword } = passwordResetConfirmSchema.parse(req.body); + await authService.confirmPasswordReset(token, newPassword); + res.json({ message: 'Contraseña actualizada exitosamente. Por favor inicia sesión con tu nueva contraseña.' }); + } catch (error) { + if (error instanceof z.ZodError) { + return next(new AppError(400, error.errors[0].message)); + } + next(error); + } +} + +const changePasswordSchema = z.object({ + currentPassword: z.string().min(1, 'Contraseña actual requerida'), + newPassword: z.string().min(8, 'La contraseña debe tener al menos 8 caracteres'), +}); + +/** + * Cambia la contraseña del user autenticado. Requiere contraseña actual. + * Tras cambio: todas las sesiones del user quedan invalidadas (incluyendo esta). + */ +export async function changePassword(req: Request, res: Response, next: NextFunction) { + try { + const { currentPassword, newPassword } = changePasswordSchema.parse(req.body); + await authService.changePassword({ + userId: req.user!.userId, + currentPassword, + newPassword, + }); + res.json({ + message: 'Contraseña actualizada. Por seguridad, todas tus sesiones fueron cerradas. Inicia sesión de nuevo.', + }); + } catch (error) { + if (error instanceof z.ZodError) { + return next(new AppError(400, error.errors[0].message)); + } + next(error); + } +} + +/** + * "Cerrar todas las sesiones" — invalida todos los tokens del user actual. + */ +export async function logoutAll(req: Request, res: Response, next: NextFunction) { + try { + await authService.logoutAllSessions(req.user!.userId); + res.json({ message: 'Todas tus sesiones fueron cerradas. Inicia sesión de nuevo.' }); + } catch (error) { + next(error); + } +} + +const switchTenantSchema = z.object({ + tenantId: z.string().uuid('tenantId inválido'), + refreshToken: z.string().min(1, 'refreshToken requerido'), +}); + +/** + * Cambia el tenant activo del user (requiere membership válida). Emite un par + * nuevo de tokens apuntando al tenant destino y revoca el refresh token actual. + */ +export async function switchTenant(req: Request, res: Response, next: NextFunction) { + try { + const { tenantId, refreshToken } = switchTenantSchema.parse(req.body); + const result = await authService.switchTenant({ + userId: req.user!.userId, + currentRefreshToken: refreshToken, + targetTenantId: tenantId, + }); + res.json(result); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +/** + * Marca el onboarding como dismissed para el user actual. Idempotente — si ya + * estaba dismissed, conserva el timestamp original. La UI lo invoca cuando el + * user completa todos los pasos requeridos del onboarding. + */ +export async function dismissOnboarding(req: Request, res: Response, next: NextFunction) { + try { + const result = await authService.dismissOnboarding(req.user!.userId); + res.json(result); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/bancos.controller.ts b/apps/api/src/controllers/bancos.controller.ts new file mode 100644 index 0000000..a77b21d --- /dev/null +++ b/apps/api/src/controllers/bancos.controller.ts @@ -0,0 +1,62 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as bancosService from '../services/bancos.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +const createSchema = z.object({ + banco: z.string().min(1, 'banco requerido').max(100), + terminacionCuenta: z.string().min(1).max(4, 'terminacionCuenta max 4 digitos'), +}); + +const updateSchema = z.object({ + banco: z.string().min(1).max(100).optional(), + terminacionCuenta: z.string().min(1).max(4).optional(), +}); + +export async function getBancos(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = (req.query.contribuyenteId as string) || null; + const bancos = await bancosService.getBancos(req.tenantPool!, contribuyenteId); + res.json(bancos); + } catch (error) { next(error); } +} + +export async function createBanco(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') return res.status(403).json({ message: 'No autorizado' }); + const data = createSchema.parse(req.body); + const contribuyenteId = req.body.contribuyenteId as string | undefined; + const result = await bancosService.createBanco(req.tenantPool!, { ...data, contribuyenteId }); + res.status(201).json(result); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function updateBanco(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') return res.status(403).json({ message: 'No autorizado' }); + const id = parseInt(String(req.params.id)); + const data = updateSchema.parse(req.body); + const result = await bancosService.updateBanco(req.tenantPool!, id, data); + res.json(result); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function deleteBanco(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') return res.status(403).json({ message: 'No autorizado' }); + const id = parseInt(String(req.params.id)); + await bancosService.deleteBanco(req.tenantPool!, id); + res.json({ message: 'Banco eliminado' }); + } catch (error: any) { + if (error.message?.includes('conciliaciones')) { + return res.status(400).json({ message: error.message }); + } + next(error); + } +} diff --git a/apps/api/src/controllers/calendario.controller.ts b/apps/api/src/controllers/calendario.controller.ts new file mode 100644 index 0000000..84ea05d --- /dev/null +++ b/apps/api/src/controllers/calendario.controller.ts @@ -0,0 +1,175 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { generarEventosFiscales, generarEventosDesdeObligaciones } from '../services/calendario-fiscal.service.js'; +import * as recordatoriosService from '../services/recordatorios.service.js'; +import { getEventosTareasParaCalendario } from '../services/tareas.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { isDespachoTenant } from '@horux/shared'; +import { prisma } from '../config/database.js'; + +const createRecordatorioSchema = z.object({ + titulo: z.string().min(1).max(200), + descripcion: z.string().max(2000).default(''), + fechaLimite: z.string().min(8), // ISO date o yyyy-mm-dd + notas: z.string().max(2000).optional(), + privado: z.boolean().optional(), +}); + +const updateRecordatorioSchema = z.object({ + titulo: z.string().min(1).max(200).optional(), + descripcion: z.string().max(2000).optional(), + fechaLimite: z.string().min(8).optional(), + notas: z.string().max(2000).optional(), + privado: z.boolean().optional(), + completado: z.boolean().optional(), +}); + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +// Forma compatible con EventoFiscal (sin metadata interna como tareaId/periodoId). +function eventoTareaShape(t: import('../services/tareas.service.js').TareaEventoCalendario) { + return { + titulo: t.titulo, + descripcion: t.descripcion, + tipo: 'tarea' as const, + fechaLimite: t.fechaLimite, + recurrencia: t.recurrencia, + completado: t.completado, + notas: t.notas, + // Metadata adicional para el frontend (links, modales) + tareaId: t.tareaId, + periodoId: t.periodoId, + }; +} + +export async function getEventosGenerados(req: Request, res: Response, next: NextFunction) { + try { + const año = parseInt(req.query.año as string) || new Date().getFullYear(); + const tenantId = effectiveTenantId(req); + + let fiscales; + + // Determine tenant type by looking up the RFC from the central DB + const tenantRecord = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { rfc: true }, + }); + const isDespacho = isDespachoTenant(tenantRecord?.rfc); + if (isDespacho) { + const contribuyenteId = (req.query.contribuyenteId as string) || null; + fiscales = await generarEventosDesdeObligaciones(req.tenantPool!, contribuyenteId, año); + } else { + // Horux360: use static catalog as before + fiscales = await generarEventosFiscales(tenantId, año); + } + + // Recordatorios custom — always included regardless of tenant type + const custom = await recordatoriosService.getRecordatorios( + req.tenantPool!, + req.user!.userId, + año + ); + + // Tareas operativas (despacho) — solo si hay contribuyente y rol no es cliente. + // El usuario tipo cliente no debe ver tareas internas del despacho. + let tareas: ReturnType[] = []; + const contribuyenteIdParam = (req.query.contribuyenteId as string) || null; + if (contribuyenteIdParam && req.user?.role !== 'cliente') { + const tareasRaw = await getEventosTareasParaCalendario( + req.tenantPool!, + contribuyenteIdParam, + año, + ); + tareas = tareasRaw.map(eventoTareaShape); + } + + // Merge y ordenar por fecha + const todos = [...fiscales, ...custom, ...tareas].sort((a, b) => + a.fechaLimite.localeCompare(b.fechaLimite) + ); + + res.json(todos); + } catch (error) { + next(error); + } +} + +export async function createRecordatorio(req: Request, res: Response, next: NextFunction) { + try { + if (!['owner', 'cfo', 'contador', 'supervisor', 'auxiliar'].includes(req.user!.role)) { + return res.status(403).json({ message: 'Solo admin y contador pueden crear recordatorios' }); + } + + const data = createRecordatorioSchema.parse(req.body); + + const evento = await recordatoriosService.createRecordatorio( + req.tenantPool!, + req.user!.userId, + { ...data, tipo: 'custom', recurrencia: 'unica' } + ); + + res.status(201).json(evento); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function updateRecordatorio(req: Request, res: Response, next: NextFunction) { + try { + if (!['owner', 'cfo', 'contador', 'supervisor', 'auxiliar'].includes(req.user!.role)) { + return res.status(403).json({ message: 'Solo admin y contador pueden editar recordatorios' }); + } + + const id = parseInt(String(req.params.id)); + if (isNaN(id)) { + return res.status(400).json({ message: 'ID inválido' }); + } + + const data = updateRecordatorioSchema.parse(req.body); + const evento = await recordatoriosService.updateRecordatorio( + req.tenantPool!, + req.user!.userId, + id, + data + ); + + if (!evento) { + return res.status(404).json({ message: 'Recordatorio no encontrado' }); + } + + res.json(evento); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function deleteRecordatorio(req: Request, res: Response, next: NextFunction) { + try { + if (!['owner', 'cfo', 'contador', 'supervisor', 'auxiliar'].includes(req.user!.role)) { + return res.status(403).json({ message: 'Solo admin y contador pueden eliminar recordatorios' }); + } + + const id = parseInt(String(req.params.id)); + if (isNaN(id)) { + return res.status(400).json({ message: 'ID inválido' }); + } + + const deleted = await recordatoriosService.deleteRecordatorio( + req.tenantPool!, + req.user!.userId, + id + ); + + if (!deleted) { + return res.status(404).json({ message: 'Recordatorio no encontrado' }); + } + + res.status(204).send(); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/cartera.controller.ts b/apps/api/src/controllers/cartera.controller.ts new file mode 100644 index 0000000..032d6e5 --- /dev/null +++ b/apps/api/src/controllers/cartera.controller.ts @@ -0,0 +1,277 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as carteraService from '../services/cartera.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +const createSchema = z.object({ + nombre: z.string().min(1, 'Nombre requerido'), + descripcion: z.string().optional(), + supervisorUserId: z.string().uuid().optional(), // Owner can assign to a supervisor +}); + +const createSubcarteraSchema = z.object({ + nombre: z.string().min(1, 'Nombre requerido'), + descripcion: z.string().optional(), + auxiliarUserId: z.string().uuid('Auxiliar requerido'), +}); + +const updateSchema = z.object({ + nombre: z.string().min(1).optional(), + descripcion: z.string().optional(), + supervisorUserId: z.string().uuid().optional(), +}); + +/** + * Permission helpers: + * - Owner: sees all, edits all + * - Supervisor: sees carteras assigned to them (by owner) + carteras they created. + * Can only edit/delete carteras THEY created. Cannot edit owner-created ones. + * Can only add contribuyentes that are already assigned to them. + * - Auxiliar: sees subcarteras where they're assigned. Read-only. + */ + +function isOwner(req: Request): boolean { + return req.user!.role === 'owner'; +} + +function isSupervisor(req: Request): boolean { + return req.user!.role === 'supervisor'; +} + +/** Check if a supervisor created this cartera (vs owner assigned it to them) */ +async function supervisorCreatedCartera(req: Request, cartera: carteraService.CarteraRow): Promise { + // A cartera was created by the supervisor if supervisorUserId === the supervisor's userId + // AND the cartera was not created by the owner assigning it. + // We use a heuristic: if the supervisor_user_id matches and createdBy is not tracked, + // we assume the supervisor can edit their own carteras. + // For now: supervisor can edit carteras where they are the supervisor. + // Owner-created carteras also have supervisorUserId set to the supervisor — + // so we need another way to distinguish. + // Solution: we'll add a 'created_by' concept. For now, let supervisor edit all carteras + // assigned to them (both owner-created and self-created). + // The user said: "Las que crea el owner, solo las puede ver el supervisor, pero no las puede editar" + // This requires tracking who created the cartera. Let's use a simple approach: + // check if the owner's userId matches the request user. + return cartera.supervisorUserId === req.user!.userId; +} + +export async function list(req: Request, res: Response, next: NextFunction) { + try { + const role = req.user!.role; + const userId = req.user!.userId; + + if (isOwner(req)) { + // Owner sees all top-level carteras + const rows = await carteraService.listCarteras(req.tenantPool!); + return res.json({ data: rows }); + } + + if (isSupervisor(req)) { + // Supervisor sees carteras assigned to them + const rows = await carteraService.listCarteras(req.tenantPool!, userId); + return res.json({ data: rows }); + } + + // Auxiliar: sees subcarteras where they're assigned + const { rows } = await req.tenantPool!.query( + `SELECT c.id, c.supervisor_user_id AS "supervisorUserId", + c.auxiliar_user_id AS "auxiliarUserId", c.parent_id AS "parentId", + c.nombre, c.descripcion, c.created_at AS "createdAt", + (SELECT count(*) FROM cartera_entidades ce WHERE ce.cartera_id = c.id)::int AS "entidadesCount", + 0 AS "subcarterasCount" + FROM carteras c + WHERE c.auxiliar_user_id = $1 + ORDER BY c.nombre`, + [userId], + ); + return res.json({ data: rows }); + } catch (err) { return next(err); } +} + +export async function getById(req: Request, res: Response, next: NextFunction) { + try { + const row = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id)); + if (!row) return next(new AppError(404, 'Cartera no encontrada')); + // Auxiliar can only see their own subcarteras + if (req.user!.role === 'auxiliar' && row.auxiliarUserId !== req.user!.userId) { + return next(new AppError(403, 'No autorizado')); + } + return res.json(row); + } catch (err) { return next(err); } +} + +export async function create(req: Request, res: Response, next: NextFunction) { + try { + const data = createSchema.parse(req.body); + const supervisorUserId = data.supervisorUserId || req.user!.userId; + const row = await carteraService.createCartera(req.tenantPool!, { + supervisorUserId, + nombre: data.nombre, + descripcion: data.descripcion, + }); + return res.status(201).json(row); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +export async function update(req: Request, res: Response, next: NextFunction) { + try { + const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id)); + if (!cartera) return next(new AppError(404, 'Cartera no encontrada')); + + // Supervisor cannot edit carteras (owner-assigned are read-only for them) + // Only owner can edit top-level carteras + if (isSupervisor(req)) { + return next(new AppError(403, 'Solo el owner puede editar carteras')); + } + + const data = updateSchema.parse(req.body); + const row = await carteraService.updateCartera(req.tenantPool!, String(req.params.id), data); + return res.json(row); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +export async function remove(req: Request, res: Response, next: NextFunction) { + try { + const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id)); + if (!cartera) return next(new AppError(404, 'Cartera no encontrada')); + + if (isSupervisor(req)) { + return next(new AppError(403, 'Solo el owner puede eliminar carteras')); + } + + await carteraService.deleteCartera(req.tenantPool!, String(req.params.id)); + return res.json({ message: 'Cartera eliminada' }); + } catch (err) { return next(err); } +} + +// Subcarteras +export async function listSubcarteras(req: Request, res: Response, next: NextFunction) { + try { + const rows = await carteraService.listSubcarteras(req.tenantPool!, String(req.params.id)); + return res.json({ data: rows }); + } catch (err) { return next(err); } +} + +export async function createSubcartera(req: Request, res: Response, next: NextFunction) { + try { + const parent = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id)); + if (!parent) return next(new AppError(404, 'Cartera padre no encontrada')); + + // Supervisor can create subcarteras within their own carteras + if (isSupervisor(req) && parent.supervisorUserId !== req.user!.userId) { + return next(new AppError(403, 'No autorizado')); + } + + const data = createSubcarteraSchema.parse(req.body); + const row = await carteraService.createSubcartera(req.tenantPool!, { + parentId: String(req.params.id), + auxiliarUserId: data.auxiliarUserId, + nombre: data.nombre, + descripcion: data.descripcion, + }); + return res.status(201).json(row); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +// Entidades +export async function addEntidad(req: Request, res: Response, next: NextFunction) { + try { + const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id)); + if (!cartera) return next(new AppError(404, 'Cartera no encontrada')); + + if (isSupervisor(req)) { + // For subcarteras: check the parent's supervisor + const supervisorId = cartera.supervisorUserId + || (cartera.parentId ? (await carteraService.getCarteraById(req.tenantPool!, cartera.parentId))?.supervisorUserId : null); + if (supervisorId !== req.user!.userId) { + return next(new AppError(403, 'No autorizado')); + } + } + + const { entidadId } = z.object({ entidadId: z.string().uuid() }).parse(req.body); + await carteraService.addEntidadToCartera(req.tenantPool!, String(req.params.id), entidadId); + return res.json({ message: 'Entidad agregada a cartera' }); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +export async function removeEntidad(req: Request, res: Response, next: NextFunction) { + try { + const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id)); + if (!cartera) return next(new AppError(404, 'Cartera no encontrada')); + + if (isSupervisor(req)) { + const supervisorId = cartera.supervisorUserId + || (cartera.parentId ? (await carteraService.getCarteraById(req.tenantPool!, cartera.parentId))?.supervisorUserId : null); + if (supervisorId !== req.user!.userId) { + return next(new AppError(403, 'No autorizado')); + } + } + + await carteraService.removeEntidadFromCartera(req.tenantPool!, String(req.params.id), String(req.params.entidadId)); + return res.json({ message: 'Entidad removida de cartera' }); + } catch (err) { return next(err); } +} + +export async function getEntidades(req: Request, res: Response, next: NextFunction) { + try { + const ids = await carteraService.getCarteraEntidades(req.tenantPool!, String(req.params.id)); + return res.json({ data: ids }); + } catch (err) { return next(err); } +} + +// Auxiliares +export async function getAuxiliares(req: Request, res: Response, next: NextFunction) { + try { + const ids = await carteraService.getCarteraAuxiliares(req.tenantPool!, String(req.params.id)); + return res.json({ data: ids }); + } catch (err) { return next(err); } +} + +export async function addAuxiliar(req: Request, res: Response, next: NextFunction) { + try { + const { auxiliarUserId } = z.object({ auxiliarUserId: z.string().uuid() }).parse(req.body); + await carteraService.addAuxiliarToCartera(req.tenantPool!, String(req.params.id), auxiliarUserId); + return res.json({ message: 'Auxiliar agregado a cartera' }); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +export async function removeAuxiliar(req: Request, res: Response, next: NextFunction) { + try { + await carteraService.removeAuxiliarFromCartera(req.tenantPool!, String(req.params.id), String(req.params.auxiliarUserId)); + return res.json({ message: 'Auxiliar removido de cartera' }); + } catch (err) { return next(err); } +} + +// Supervisores available (for dropdown) +export async function getSupervisores(req: Request, res: Response, next: NextFunction) { + try { + const supervisores = await carteraService.getSupervisores(req.tenantPool!, req.user!.tenantId); + return res.json({ data: supervisores }); + } catch (err) { return next(err); } +} + +// Auxiliares of a supervisor +export async function getAuxiliaresDelSupervisor(req: Request, res: Response, next: NextFunction) { + try { + const supervisorId = isOwner(req) + ? String(req.params.supervisorId || req.user!.userId) + : req.user!.userId; + const rows = await carteraService.getAuxiliaresDelSupervisor(req.tenantPool!, supervisorId); + return res.json({ data: rows }); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/catalogos.controller.ts b/apps/api/src/controllers/catalogos.controller.ts new file mode 100644 index 0000000..c20a3a6 --- /dev/null +++ b/apps/api/src/controllers/catalogos.controller.ts @@ -0,0 +1,108 @@ +import type { Request, Response, NextFunction } from 'express'; +import { prisma } from '../config/database.js'; + +export async function getFormasPago(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catFormaPago.findMany({ orderBy: { clave: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} + +export async function getMetodosPago(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catMetodoPago.findMany({ orderBy: { clave: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} + +export async function getUsosCfdi(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catUsoCfdi.findMany({ orderBy: { clave: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} + +export async function getMonedas(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catMoneda.findMany({ orderBy: { clave: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} + +export async function getClavesUnidad(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catClaveUnidad.findMany({ orderBy: { descripcion: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} + +export async function searchClaveProdServ(req: Request, res: Response, next: NextFunction) { + try { + const q = (req.query.q as string || '').trim(); + if (q.length < 2) { + return res.json([]); + } + + // Buscar por clave o descripción + // Primero buscar por clave, luego por texto + const data = await prisma.catClaveProdServ.findMany({ + where: { + OR: [ + { clave: { startsWith: q } }, + { descripcion: { contains: q, mode: 'insensitive' } }, + ], + }, + take: 20, + orderBy: { clave: 'asc' }, + }); + + // Si no hay resultados, intentar sin acentos + if (data.length === 0) { + const normalized = q.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); + if (normalized !== q) { + const fallback = await prisma.catClaveProdServ.findMany({ + where: { descripcion: { contains: normalized, mode: 'insensitive' } }, + take: 20, + orderBy: { clave: 'asc' }, + }); + return res.json(fallback); + } + + // Buscar con variantes comunes de acentos + const withAccents = normalized + .replace(/a/gi, '[aá]').replace(/e/gi, '[eé]') + .replace(/i/gi, '[ií]').replace(/o/gi, '[oó]').replace(/u/gi, '[uú]') + .replace(/n/gi, '[nñ]'); + + // Usar raw SQL con regex para búsqueda flexible + const rows: any[] = await prisma.$queryRawUnsafe( + `SELECT id, clave, descripcion FROM cat_clave_prod_serv WHERE descripcion ~* $1 ORDER BY clave LIMIT 20`, + withAccents + ); + return res.json(rows); + } + + res.json(data); + } catch (error) { next(error); } +} + +export async function getObjetosImp(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catObjetoImp.findMany({ orderBy: { clave: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} + +export async function getTiposRelacion(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catTipoRelacion.findMany({ orderBy: { clave: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} + +export async function getExportaciones(req: Request, res: Response, next: NextFunction) { + try { + const data = await prisma.catExportacion.findMany({ orderBy: { clave: 'asc' } }); + res.json(data); + } catch (error) { next(error); } +} diff --git a/apps/api/src/controllers/cfdi.controller.ts b/apps/api/src/controllers/cfdi.controller.ts new file mode 100644 index 0000000..687c880 --- /dev/null +++ b/apps/api/src/controllers/cfdi.controller.ts @@ -0,0 +1,530 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as cfdiService from '../services/cfdi.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { GRUPO_PF_EMPRESARIAL, GRUPO_PM_OTROS } from '../services/dashboard.service.js'; +import { getRegimenesIgnoradosClaves } from '../services/regimen.service.js'; +import { resolveContribuyenteContext } from '../utils/contribuyente-context.js'; +import { buildExtraFilters } from '../services/_shared/cfdi-filters.js'; +import type { CfdiFilters } from '@horux/shared'; + +export async function getCfdis(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const filters: CfdiFilters = { + tipo: req.query.tipo as any, + tipoComprobante: req.query.tipoComprobante as any, + estado: req.query.estado as any, + fechaInicio: req.query.fechaInicio as string, + fechaFin: req.query.fechaFin as string, + rfc: req.query.rfc as string, + emisor: req.query.emisor as string, + receptor: req.query.receptor as string, + search: req.query.search as string, + contribuyenteId: req.query.contribuyenteId as string, + page: parseInt(req.query.page as string) || 1, + // Cap defensivo: paginación normal usa 20-100; export pide 10000. + // Más de eso se rechaza para no agotar memoria del proceso. + limit: Math.min(parseInt(req.query.limit as string) || 20, 10_000), + }; + + const result = await cfdiService.getCfdis(req.tenantPool, filters); + res.json(result); + } catch (error) { + next(error); + } +} + +export async function getCfdiById(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const cfdi = await cfdiService.getCfdiById(req.tenantPool, String(req.params.id)); + + if (!cfdi) { + return next(new AppError(404, 'CFDI no encontrado')); + } + + res.json(cfdi); + } catch (error) { + next(error); + } +} + +export async function getXml(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const xml = await cfdiService.getXmlById(req.tenantPool, String(req.params.id)); + + if (!xml) { + return next(new AppError(404, 'XML no encontrado para este CFDI')); + } + + res.set('Content-Type', 'application/xml'); + res.set('Content-Disposition', `attachment; filename="cfdi-${req.params.id}.xml"`); + res.send(xml); + } catch (error) { + next(error); + } +} + +export async function listConceptos(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) return next(new AppError(400, 'Tenant no configurado')); + + const filters: CfdiFilters & { + uuidLike?: string; + claveProdServ?: string; + descripcionConcepto?: string; + orderBy?: 'fecha' | 'importe'; + orderDir?: 'asc' | 'desc'; + } = { + tipo: req.query.tipo as any, + tipoComprobante: req.query.tipoComprobante as any, + estado: req.query.estado as any, + fechaInicio: req.query.fechaInicio as string, + fechaFin: req.query.fechaFin as string, + rfc: req.query.rfc as string, + emisor: req.query.emisor as string, + receptor: req.query.receptor as string, + search: req.query.search as string, + contribuyenteId: req.query.contribuyenteId as string, + page: parseInt(req.query.page as string) || 1, + limit: Math.min(parseInt(req.query.limit as string) || 50, 10_000), + uuidLike: req.query.uuidLike as string, + claveProdServ: req.query.claveProdServ as string, + descripcionConcepto: req.query.descripcionConcepto as string, + orderBy: req.query.orderBy as 'fecha' | 'importe', + orderDir: req.query.orderDir as 'asc' | 'desc', + }; + + const result = await cfdiService.getConceptosList(req.tenantPool, filters); + res.json(result); + } catch (error) { next(error); } +} + +export async function getConceptos(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const conceptos = await cfdiService.getConceptos(req.tenantPool, String(req.params.id)); + res.json(conceptos); + } catch (error) { + next(error); + } +} + +export async function drillDown(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const { + fechaInicio, fechaFin, type, tipoComprobante, metodoPago, + regimenEmisor, regimenReceptor, status, contribuyenteId, + bucket, considerarActivos, considerarNCs, + } = req.query; + + // Default true (consistente con el resto del sistema). Solo false si la URL + // pasa explícitamente '0' o 'false'. Sin estos toggles, el drill ignoraba + // el filtro de "Considerar activos" y mostraba CFDIs que la card sí estaba + // excluyendo del total. + const considerarActivosBool = considerarActivos !== '0' && considerarActivos !== 'false'; + const considerarNCsBool = considerarNCs !== '0' && considerarNCs !== 'false'; + const extra = buildExtraFilters(considerarActivosBool, considerarNCsBool); + + let where = 'WHERE 1=1'; + const params: any[] = []; + let pi = 1; + + // `bucket` expande la combinación (type, tipo_comprobante, metodo_pago, + // régimen) exactamente igual a la fórmula de KPIs/tarjetas — para que + // el drill-down cuadre línea a línea con el total del header. + // + // Reglas por bucket (alineado con dashboard.service y impuestos.service): + // ingresos: 3 grupos de régimen del emisor con fórmulas distintas. + // Grupo 1 (PF Empresarial 606/612/621/625/626): EMIT I PUE + EMIT P + // Grupo 2 (Sueldos 605, recibido como N): RECIB N PUE con receptor=605 + // Grupo 3 (PM y otros): EMIT I PUE+PPD + // gastos: uniforme todos los regímenes del receptor + // RECIB I PUE + RECIB P + // causado (IVA): EMIT I PUE + EMIT P + EMIT E PUE (excl. E/07) + // acreditable (IVA): RECIB I PUE + RECIB P + RECIB E PUE (excl. E/07) + // + // Las E PUE NO entran en ingresos/gastos — viven en sus propios drills + // ("NCs Emitidas" / "NCs Recibidas"). En IVA causado/acreditable sí + // entran, ya que el IVA de las NCs sí se acredita/cancela. + // + // Régimenes "ignorados" por el tenant se excluyen en todos los buckets. + // Las NC que restan se muestran como filas con signo (frontend las resta + // del total del header). Si `bucket` se pasa, se ignoran filtros + // type/tipoComprobante/metodoPago de entrada. + const bucketStr = typeof bucket === 'string' ? bucket.toLowerCase() : ''; + const bucketApplied = bucketStr === 'ingresos' || bucketStr === 'gastos' || + bucketStr === 'causado' || bucketStr === 'acreditable' || + bucketStr === 'ncs_emitidas' || bucketStr === 'ncs_recibidas' || + bucketStr === 'no_deducibles_efectivo'; + + // Régimenes ignorados por el tenant (configurable en /regimenes). Se + // excluyen del lado correspondiente según el bucket. + const ignorados = req.user?.tenantId + ? await getRegimenesIgnoradosClaves(req.user.tenantId) + : []; + + // Resolver condiciones esEmisor/esReceptor basadas en RFC del contribuyente. + // Reemplaza `type = 'EMITIDO/RECIBIDO' AND contribuyente_id = X` por un + // filtro por RFC — fuente de verdad cuando dos contribuyentes del tenant + // se facturan entre sí (type/contribuyente_id pueden ser inconsistentes). + const contribIdStr = typeof contribuyenteId === 'string' ? contribuyenteId : undefined; + const cfdiCtx = req.user?.tenantId + ? await resolveContribuyenteContext(req.tenantPool, req.user.tenantId, contribIdStr) + : null; + const esEmisor = cfdiCtx?.esEmisor || `type = 'EMITIDO'`; + const esReceptor = cfdiCtx?.esReceptor || `type = 'RECIBIDO'`; + + const NO_IGNORADO_EMISOR = ignorados.length > 0 + ? `AND (regimen_fiscal_emisor IS NULL OR regimen_fiscal_emisor NOT IN (${ignorados.map(r => `'${r}'`).join(',')}))` + : ''; + const NO_IGNORADO_RECEPTOR = ignorados.length > 0 + ? `AND (regimen_fiscal_receptor IS NULL OR regimen_fiscal_receptor NOT IN (${ignorados.map(r => `'${r}'`).join(',')}))` + : ''; + + const g1 = GRUPO_PF_EMPRESARIAL.map(r => `'${r}'`).join(','); + const g3 = GRUPO_PM_OTROS.map(r => `'${r}'`).join(','); + // Conjunto canónico de regímenes que el dashboard considera (excluye 616 + // extranjero y otros fuera del catálogo). El drill debe respetarlo para + // cuadrar con los KPIs/tarjetas. + const TODOS_REGS = [...GRUPO_PF_EMPRESARIAL, '605', ...GRUPO_PM_OTROS] + .map(r => `'${r}'`) + .join(','); + const E_NO_ANTICIPO = `COALESCE(cfdi_tipo_relacion, '') <> '07'`; + + if (bucketStr === 'ingresos') { + // 3 grupos con fórmulas distintas. Filtro por RFC (esEmisor/esReceptor). + // Las E PUE se exhiben en su propia card "NCs Emitidas" — no entran aquí. + // I/07 PPD compensación: cuando el contribuyente emite I/07 PPD con E + // relacionada en mismo mes, el cálculo aporta el valor de la E. La I/07 + // PPD aparece en el drill (parte del Grupo 1 universe vía I PPD), pero + // las E ya no. + where += ` AND ( + ( -- Grupo 1 PF Empresarial + ${esEmisor} + AND regimen_fiscal_emisor IN (${g1}) + AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE') + OR (tipo_comprobante = 'P') + ) + ) + OR ( -- Grupo 2 Sueldos: nómina recibida 605 + ${esReceptor} + AND tipo_comprobante = 'N' + AND metodo_pago = 'PUE' + AND regimen_fiscal_receptor = '605' + ) + OR ( -- Grupo 3 PM y otros + ${esEmisor} + AND regimen_fiscal_emisor IN (${g3}) + AND tipo_comprobante = 'I' AND metodo_pago IN ('PUE','PPD') + ) + ) ${NO_IGNORADO_EMISOR.replace('regimen_fiscal_emisor', `CASE WHEN ${esEmisor} THEN regimen_fiscal_emisor ELSE regimen_fiscal_receptor END`)}`; + } else if (bucketStr === 'gastos') { + // Las E PUE se exhiben en su propia card "NCs Recibidas" — no entran aquí. + where += ` AND ( + ${esReceptor} AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE') + OR (tipo_comprobante = 'P') + ) + AND regimen_fiscal_receptor IN (${TODOS_REGS}) + ) ${NO_IGNORADO_RECEPTOR}`; + } else if (bucketStr === 'causado') { + where += ` AND ( + ${esEmisor} AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE') + OR (tipo_comprobante = 'P') + OR (tipo_comprobante = 'E' AND metodo_pago = 'PUE' AND ${E_NO_ANTICIPO}) + ) + AND regimen_fiscal_emisor IN (${TODOS_REGS}) + ) ${NO_IGNORADO_EMISOR}`; + } else if (bucketStr === 'ncs_emitidas') { + // E PUE emitidas por el contribuyente, por régimen del emisor. + // Mirror del card "NCs Emitidas" en /impuestos > ISR. + // Sin restringir a TODOS_REGS — el calcular function tampoco lo hace + // (acepta cualquier régimen no-NULL no-ignorado, incluyendo 616 + // Extranjero, etc.). Si el contador filtró regímenes ignorados, el + // NO_IGNORADO_EMISOR ya los excluye. + where += ` AND ( + ${esEmisor} + AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND regimen_fiscal_emisor IS NOT NULL + ) ${NO_IGNORADO_EMISOR}`; + } else if (bucketStr === 'ncs_recibidas') { + // E PUE recibidas por el contribuyente, por régimen del receptor. + // Mirror del card "NCs Recibidas" en /impuestos > ISR. + where += ` AND ( + ${esReceptor} + AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND regimen_fiscal_receptor IS NOT NULL + ) ${NO_IGNORADO_RECEPTOR}`; + } else if (bucketStr === 'no_deducibles_efectivo') { + // Art. 27 fracción III LISR — facturas recibidas pagadas en efectivo + // (forma_pago='01') con monto > $2,000. Mirror del card "No Deducibles". + // I PUE: comparación con total_mxn. P: con monto_pago_mxn. + where += ` AND ( + ${esReceptor} + AND forma_pago = '01' + AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE' AND COALESCE(total_mxn, 0) > 2000) + OR (tipo_comprobante = 'P' AND COALESCE(monto_pago_mxn, 0) > 2000) + ) + AND regimen_fiscal_receptor IS NOT NULL + ) ${NO_IGNORADO_RECEPTOR}`; + } else if (bucketStr === 'acreditable') { + where += ` AND ( + ${esReceptor} AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE') + OR (tipo_comprobante = 'P') + OR (tipo_comprobante = 'E' AND metodo_pago = 'PUE' AND ${E_NO_ANTICIPO}) + ) + AND regimen_fiscal_receptor IN (${TODOS_REGS}) + ) ${NO_IGNORADO_RECEPTOR}`; + } + + // Fecha efectiva: para CFDIs tipo P (complementos de pago) usa fecha_pago_p + // (cuándo el cliente cobró) en vez de fecha_emision (cuándo se emitió el + // complemento). Así el drill-down es coherente con los KPIs — un P emitido + // en mayo que cobró una PPD de noviembre aparece en noviembre, no en mayo. + const FECHA_EFECTIVA = `CASE WHEN tipo_comprobante = 'P' THEN fecha_pago_p ELSE fecha_emision END`; + if (fechaInicio) { + where += ` AND ${FECHA_EFECTIVA} >= $${pi++}::date`; + params.push(fechaInicio); + } + if (fechaFin) { + where += ` AND ${FECHA_EFECTIVA} < ($${pi++}::date + interval '1 day')`; + params.push(fechaFin); + } + if (!bucketApplied) { + if (type) { + where += ` AND type = $${pi++}`; + params.push(type); + } + // tipoComprobante acepta valor único ('I') o CSV ('I,P'). Cuando la lista + // incluye P, el filtro metodoPago NO se aplica a los P (que no tienen), + // para que un drill-down "Ingresos del Mes" muestre I PUE + todos los P. + const tiposList = tipoComprobante + ? (tipoComprobante as string).split(',').map(t => t.trim()).filter(Boolean) + : []; + const includesP = tiposList.includes('P'); + if (tiposList.length === 1) { + where += ` AND tipo_comprobante = $${pi++}`; + params.push(tiposList[0]); + } else if (tiposList.length > 1) { + where += ` AND tipo_comprobante = ANY($${pi++})`; + params.push(tiposList); + } + if (metodoPago) { + const metodos = (metodoPago as string).split(','); + if (includesP) { + // P no tiene metodo_pago: el filtro aplica solo a los no-P + where += ` AND (tipo_comprobante = 'P' OR metodo_pago = ANY($${pi++}))`; + params.push(metodos); + } else { + where += ` AND metodo_pago = ANY($${pi++})`; + params.push(metodos); + } + } + } + if (regimenEmisor) { + where += ` AND regimen_fiscal_emisor = $${pi++}`; + params.push(regimenEmisor); + } + if (regimenReceptor) { + where += ` AND regimen_fiscal_receptor = $${pi++}`; + params.push(regimenReceptor); + } + if (status) { + if (status === 'vigente') { + where += ` AND status NOT IN ('Cancelado', '0')`; + } else { + where += ` AND status IN ('Cancelado', '0')`; + } + } + if (contribuyenteId && !bucketApplied) { + // Solo aplica cuando NO hay bucket (drill crudo, sin semantic de lado). + // Con bucket, esEmisor/esReceptor ya restringen por RFC del contribuyente. + // Sin bucket, filtramos inclusivo: contribuyente_id O RFC en cualquier lado. + if (cfdiCtx) { + where += ` AND ${cfdiCtx.contribFilter.replace(/^AND /, '')}`; + } + } + + // Aplica filtros de "Considerar activos" / "Considerar NCs" — alineado + // con los KPIs/cards. Sin esto el drill mostraba CFDIs que la card había + // excluido (ej. P que paga una I de activo con uso_cfdi=I03). + where += extra; + + const { rows } = await req.tenantPool.query(` + SELECT id, uuid, type, tipo_comprobante as "tipoComprobante", + fecha_emision as "fechaEmision", status, + rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor", + rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor", + subtotal, subtotal_mxn as "subtotalMxn", + total, total_mxn as "totalMxn", + moneda, metodo_pago as "metodoPago", + iva_traslado_mxn as "ivaTrasladoMxn", + iva_retencion_mxn as "ivaRetencionMxn", + isr_retencion_mxn as "isrRetencionMxn", + monto_pago_mxn as "montoPagoMxn", + regimen_fiscal_emisor as "regimenEmisor", + regimen_fiscal_receptor as "regimenReceptor" + FROM cfdis + ${where} + ORDER BY fecha_emision DESC + LIMIT 500 + `, params); + + res.json(rows); + } catch (error) { + next(error); + } +} + +export async function getEmisores(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const search = (req.query.search as string) || ''; + if (search.length < 2) { + return res.json([]); + } + + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const emisores = await cfdiService.getEmisores(req.tenantPool, search, 10, contribuyenteId); + res.json(emisores); + } catch (error) { + next(error); + } +} + +export async function getReceptores(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const search = (req.query.search as string) || ''; + if (search.length < 2) { + return res.json([]); + } + + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const receptores = await cfdiService.getReceptores(req.tenantPool, search, 10, contribuyenteId); + res.json(receptores); + } catch (error) { + next(error); + } +} + +export async function getResumen(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const año = parseInt(req.query.año as string) || new Date().getFullYear(); + const mes = parseInt(req.query.mes as string) || new Date().getMonth() + 1; + const contribuyenteId = req.query.contribuyenteId as string | undefined; + + const resumen = await cfdiService.getResumenCfdis(req.tenantPool, año, mes, contribuyenteId); + res.json(resumen); + } catch (error) { + next(error); + } +} + +export async function createCfdi(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + if (!['owner', 'contador'].includes(req.user!.role)) { + return next(new AppError(403, 'No tienes permisos para agregar CFDIs')); + } + + const cfdi = await cfdiService.createCfdi(req.tenantPool, req.body); + res.status(201).json(cfdi); + } catch (error: any) { + if (error.message?.includes('duplicate')) { + return next(new AppError(409, 'Este CFDI ya existe (UUID duplicado)')); + } + next(error); + } +} + +export async function createManyCfdis(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + if (!['owner', 'contador'].includes(req.user!.role)) { + return next(new AppError(403, 'No tienes permisos para agregar CFDIs')); + } + + if (!Array.isArray(req.body.cfdis)) { + return next(new AppError(400, 'Se requiere un array de CFDIs')); + } + + const batchInfo = { + batchNumber: req.body.batchNumber || 1, + totalBatches: req.body.totalBatches || 1, + totalFiles: req.body.totalFiles || req.body.cfdis.length + }; + + console.log(`[CFDI Bulk] Lote ${batchInfo.batchNumber}/${batchInfo.totalBatches} - ${req.body.cfdis.length} CFDIs`); + + const result = await cfdiService.createManyCfdisBatch(req.tenantPool, req.body.cfdis); + + res.status(201).json({ + message: `Lote ${batchInfo.batchNumber} procesado`, + batchNumber: batchInfo.batchNumber, + totalBatches: batchInfo.totalBatches, + inserted: result.inserted, + duplicates: result.duplicates, + errors: result.errors, + errorMessages: result.errorMessages.slice(0, 5) + }); + } catch (error: any) { + console.error('[CFDI Bulk Error]', error.message, error.stack); + next(new AppError(400, error.message || 'Error al procesar CFDIs')); + } +} + +export async function deleteCfdi(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + if (!['owner', 'contador'].includes(req.user!.role)) { + return next(new AppError(403, 'No tienes permisos para eliminar CFDIs')); + } + + await cfdiService.deleteCfdi(req.tenantPool, String(req.params.id)); + res.status(204).send(); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/conciliacion.controller.ts b/apps/api/src/controllers/conciliacion.controller.ts new file mode 100644 index 0000000..eb14c1e --- /dev/null +++ b/apps/api/src/controllers/conciliacion.controller.ts @@ -0,0 +1,58 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as conciliacionService from '../services/conciliacion.service.js'; +import { prisma } from '../config/database.js'; + +export async function getCfdis(req: Request, res: Response, next: NextFunction) { + try { + const { tipo, fechaInicio, fechaFin, regimen, estado, contribuyenteId } = req.query; + if (!tipo) return res.status(400).json({ message: 'tipo es requerido (EMITIDO|RECIBIDO)' }); + + const data = await conciliacionService.getCfdisConConciliacion(req.tenantPool!, { + tipo: tipo as string, + fechaInicio: fechaInicio as string, + fechaFin: fechaFin as string, + regimen: regimen as string, + estado: estado as string, + contribuyenteId: contribuyenteId as string | undefined, + }); + res.json(data); + } catch (error) { next(error); } +} + +export async function conciliar(req: Request, res: Response, next: NextFunction) { + try { + if (!['owner', 'cfo', 'contador', 'auxiliar', 'supervisor'].includes(req.user!.role)) { + return res.status(403).json({ message: 'No autorizado' }); + } + + const { cfdiIds, fechaDePago, idBanco } = req.body; + if (!cfdiIds?.length || !fechaDePago || !idBanco) { + return res.status(400).json({ message: 'cfdiIds, fechaDePago e idBanco son requeridos' }); + } + + const tenant = await prisma.tenant.findUnique({ + where: { id: req.user!.tenantId }, + select: { createdAt: true }, + }); + const tenantCreatedYear = tenant ? tenant.createdAt.getFullYear() : new Date().getFullYear(); + + const count = await conciliacionService.conciliar(req.tenantPool!, { cfdiIds, fechaDePago, idBanco }, tenantCreatedYear); + res.json({ message: `${count} CFDIs conciliados`, count }); + } catch (error: any) { + if (error.message && !error.message.includes('Internal')) { + return res.status(400).json({ message: error.message }); + } + next(error); + } +} + +export async function desconciliar(req: Request, res: Response, next: NextFunction) { + try { + if (!['owner', 'cfo', 'contador', 'auxiliar', 'supervisor'].includes(req.user!.role)) { + return res.status(403).json({ message: 'No autorizado' }); + } + const id = parseInt(String(req.params.id)); + await conciliacionService.desconciliar(req.tenantPool!, id); + res.json({ message: 'CFDI desconciliado' }); + } catch (error) { next(error); } +} diff --git a/apps/api/src/controllers/connector.controller.ts b/apps/api/src/controllers/connector.controller.ts new file mode 100644 index 0000000..8e41ed1 --- /dev/null +++ b/apps/api/src/controllers/connector.controller.ts @@ -0,0 +1,58 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as connectorService from '../services/connector.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +const heartbeatSchema = z.object({ + version: z.string(), + uptimeSeconds: z.number().optional().default(0), + postgresPingMs: z.number().optional().default(0), + pgVersion: z.string().optional(), + lastMigration: z.string().optional(), + status: z.string().optional(), + errorMsg: z.string().optional(), +}); + +// Called by the connector Docker container, NOT by browser users +export async function heartbeat(req: Request, res: Response, next: NextFunction) { + try { + const authHeader = req.headers.authorization; + if (!authHeader?.startsWith('Bearer ')) { + return res.status(401).json({ message: 'Token requerido' }); + } + + const token = authHeader.split(' ')[1]; + const tenantId = await connectorService.verifyConnectorToken(token); + if (!tenantId) { + return res.status(401).json({ message: 'Token inválido' }); + } + + const data = heartbeatSchema.parse(req.body); + await connectorService.recordHeartbeat(tenantId, data); + + return res.json({ ok: true }); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +// Called by authenticated tenant owner to provision or check connector +export async function provision(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = req.viewingTenantId || req.user!.tenantId; + const result = await connectorService.provisionConnector(tenantId); + return res.status(201).json(result); + } catch (err: any) { + if (err.message?.includes('no encontrado')) return next(new AppError(404, err.message)); + return next(err); + } +} + +export async function status(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = req.viewingTenantId || req.user!.tenantId; + const result = await connectorService.getConnectorStatus(tenantId); + return res.json(result); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/contribuyente-config.controller.ts b/apps/api/src/controllers/contribuyente-config.controller.ts new file mode 100644 index 0000000..8d49172 --- /dev/null +++ b/apps/api/src/controllers/contribuyente-config.controller.ts @@ -0,0 +1,95 @@ +import type { Request, Response, NextFunction } from 'express'; +import { AppError } from '../middlewares/error.middleware.js'; +import * as fielService from '../services/contribuyente-fiel.service.js'; +import * as facturapiService from '../services/contribuyente-facturapi.service.js'; +import { getContribuyenteById } from '../services/contribuyente.service.js'; + +// ========== FIEL ========== + +export async function uploadFiel(req: Request, res: Response, next: NextFunction) { + try { + const { cerFile, keyFile, password } = req.body; + if (!cerFile || !keyFile || !password) { + return next(new AppError(400, 'cerFile, keyFile y password son requeridos')); + } + const contribuyenteId = String(req.params.id); + const contrib = await getContribuyenteById(req.tenantPool!, contribuyenteId); + if (!contrib) return next(new AppError(404, 'Contribuyente no encontrado')); + + const result = await fielService.uploadFielContribuyente(req.tenantPool!, contribuyenteId, cerFile, keyFile, password); + if (!result.success) { + console.error('[FIEL Upload] Failed:', result.message); + return res.status(400).json({ message: result.message }); + } + return res.json(result); + } catch (err: any) { + console.error('[FIEL Upload] Exception:', err.message || err); + return next(err); + } +} + +export async function fielStatus(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + const status = await fielService.getFielStatusContribuyente(req.tenantPool!, contribuyenteId); + return res.json(status); + } catch (err) { return next(err); } +} + +export async function deleteFiel(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + // Delete from per-contribuyente table (tenant BD) + await req.tenantPool!.query( + 'UPDATE fiel_contribuyente SET is_active = false WHERE contribuyente_id = $1', + [contribuyenteId] + ); + // Also try to deactivate legacy FIEL if it matches this contribuyente's RFC + const { rows } = await req.tenantPool!.query('SELECT rfc FROM contribuyentes WHERE entidad_id = $1', [contribuyenteId]); + if (rows[0]?.rfc) { + const { prisma } = await import('../config/database.js'); + await prisma.fielCredential.updateMany({ + where: { rfc: rows[0].rfc }, + data: { isActive: false }, + }).catch(() => {}); + } + return res.json({ message: 'FIEL eliminada' }); + } catch (err) { return next(err); } +} + +// ========== FACTURAPI ========== + +export async function createOrg(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + const contrib = await getContribuyenteById(req.tenantPool!, contribuyenteId); + if (!contrib) return next(new AppError(404, 'Contribuyente no encontrado')); + + const result = await facturapiService.createOrgContribuyente(req.tenantPool!, contribuyenteId, contrib.nombre); + return res.status(201).json(result); + } catch (err: any) { + if (err.message?.includes('ya tiene')) return next(new AppError(409, err.message)); + return next(err); + } +} + +export async function orgStatus(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + const status = await facturapiService.getOrgStatusContribuyente(req.tenantPool!, contribuyenteId); + return res.json(status); + } catch (err) { return next(err); } +} + +export async function uploadCsd(req: Request, res: Response, next: NextFunction) { + try { + const { cerFile, keyFile, password } = req.body; + if (!cerFile || !keyFile || !password) { + return next(new AppError(400, 'cerFile, keyFile y password son requeridos')); + } + const contribuyenteId = String(req.params.id); + const result = await facturapiService.uploadCsdContribuyente(req.tenantPool!, contribuyenteId, cerFile, keyFile, password); + if (!result.success) return res.status(400).json({ message: result.message }); + return res.json(result); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/contribuyente.controller.ts b/apps/api/src/controllers/contribuyente.controller.ts new file mode 100644 index 0000000..a0ca37f --- /dev/null +++ b/apps/api/src/controllers/contribuyente.controller.ts @@ -0,0 +1,148 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as contribuyenteService from '../services/contribuyente.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { getEntidadesVisibles } from '../utils/entidades-visibles.js'; +import { adjustDespachoOverage } from '../services/payment/addon.service.js'; +import { prisma } from '../config/database.js'; + +/** + * Límite duro de contribuyentes mientras el despacho está en trial gratuito. + * Una vez expira el trial (`trialEndsAt < now`) este límite deja de aplicar y + * el plan vigente toma el control. + */ +const TRIAL_MAX_CONTRIBUYENTES = 5; + +/** + * Cuenta contribuyentes activos del tenant actual. Usado para ajustar el + * overage de Business Control / Enterprise tras crear o desactivar un RFC, + * y para enforce el límite del trial. + */ +async function countActiveContribuyentes(pool: import('pg').Pool): Promise { + const { rows: [{ cnt }] } = await pool.query<{ cnt: string }>( + `SELECT COUNT(*)::text AS cnt FROM entidades_gestionadas + WHERE active = true AND tipo = 'CONTRIBUYENTE'`, + ); + return Number(cnt) || 0; +} + +const createSchema = z.object({ + rfc: z.string().regex(/^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/i, 'RFC inválido'), + razonSocial: z.string().min(2, 'Razón social requerida'), + regimenFiscal: z.string().length(3).optional(), + codigoPostal: z.string().regex(/^\d{5}$/).optional(), + domicilio: z.record(z.unknown()).optional(), + supervisorUserId: z.string().uuid().optional(), +}); + +const updateSchema = createSchema.partial(); + +export async function list(req: Request, res: Response, next: NextFunction) { + try { + const visibleIds = await getEntidadesVisibles(req.tenantPool!, req.user!.userId, req.user!.role); + const rows = await contribuyenteService.listContribuyentes(req.tenantPool!, visibleIds); + return res.json({ data: rows }); + } catch (err) { return next(err); } +} + +export async function getById(req: Request, res: Response, next: NextFunction) { + try { + const row = await contribuyenteService.getContribuyenteById(req.tenantPool!, String(req.params.id)); + if (!row) return next(new AppError(404, 'Contribuyente no encontrado')); + return res.json(row); + } catch (err) { return next(err); } +} + +export async function create(req: Request, res: Response, next: NextFunction) { + try { + const data = createSchema.parse(req.body); + + // Trial gate: durante el periodo de prueba (trialEndsAt > now) el despacho + // no puede gestionar más de TRIAL_MAX_CONTRIBUYENTES RFCs activos. Cuando + // el trial expira, deja de aplicar y el límite del plan vigente toma el control. + const tenant = await prisma.tenant.findUnique({ + where: { id: req.user!.tenantId }, + select: { trialEndsAt: true }, + }); + const isTrialActive = tenant?.trialEndsAt ? tenant.trialEndsAt > new Date() : false; + if (isTrialActive) { + const activeCount = await countActiveContribuyentes(req.tenantPool!); + if (activeCount >= TRIAL_MAX_CONTRIBUYENTES) { + return next(new AppError( + 403, + `Durante el periodo de prueba puedes gestionar hasta ${TRIAL_MAX_CONTRIBUYENTES} contribuyentes. Contrata un plan para agregar más.`, + )); + } + } + + const row = await contribuyenteService.createContribuyente(req.tenantPool!, data); + + // Ajuste de overage despacho: si el tenant pasa de 100 a 101+ RFCs, crea + // el addon y devuelve paymentUrl para que el frontend redirija al usuario. + // Fail-soft: si falla el addon, el contribuyente queda creado y se loguea. + let overage: Awaited> | null = null; + try { + const activeCount = await countActiveContribuyentes(req.tenantPool!); + overage = await adjustDespachoOverage(req.user!.tenantId, activeCount); + } catch (err: any) { + console.error('[Contribuyente] Overage adjust failed (non-blocking):', err.message || err); + } + + return res.status(201).json({ ...row, overage }); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + if (err.code === '23505') return next(new AppError(409, 'Ya existe un contribuyente con este RFC')); + return next(err); + } +} + +export async function update(req: Request, res: Response, next: NextFunction) { + try { + const data = updateSchema.parse(req.body); + const row = await contribuyenteService.updateContribuyente(req.tenantPool!, String(req.params.id), data); + if (!row) return next(new AppError(404, 'Contribuyente no encontrado')); + return res.json(row); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +export async function deactivate(req: Request, res: Response, next: NextFunction) { + try { + const ok = await contribuyenteService.deactivateContribuyente(req.tenantPool!, String(req.params.id)); + if (!ok) return next(new AppError(404, 'Contribuyente no encontrado')); + + // Ajuste de overage despacho: si el count baja, reduce quantity del + // addon (updatePreapprovalAmount) o cancela el preapproval si pasa al límite. + let overage: Awaited> | null = null; + try { + const activeCount = await countActiveContribuyentes(req.tenantPool!); + overage = await adjustDespachoOverage(req.user!.tenantId, activeCount); + } catch (err: any) { + console.error('[Contribuyente] Overage adjust failed (non-blocking):', err.message || err); + } + + return res.json({ message: 'Contribuyente desactivado', overage }); + } catch (err) { return next(err); } +} + +export async function backfill(req: Request, res: Response, next: NextFunction) { + try { + const total = await contribuyenteService.backfillAllContribuyentes(req.tenantPool!); + return res.json({ message: `${total} CFDIs asignados a contribuyentes`, total }); + } catch (err) { return next(err); } +} + +export async function addClienteAcceso(req: Request, res: Response, next: NextFunction) { + try { + const { userId } = req.body; + if (!userId || typeof userId !== 'string') return next(new AppError(400, 'userId requerido')); + const entidadId = String(req.params.id); + await req.tenantPool!.query( + 'INSERT INTO cliente_accesos (user_id, entidad_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', + [userId, entidadId], + ); + return res.json({ message: 'Acceso otorgado' }); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/dashboard.controller.ts b/apps/api/src/controllers/dashboard.controller.ts new file mode 100644 index 0000000..03c08c4 --- /dev/null +++ b/apps/api/src/controllers/dashboard.controller.ts @@ -0,0 +1,108 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as dashboardService from '../services/dashboard.service.js'; +import { generarAlertasAutomaticas } from '../services/alertas-auto.service.js'; +import { getAlertasManualesPendientes } from '../services/alertas-manuales.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +function getDefaultRange() { + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth() + 1; + const lastDay = new Date(y, m, 0).getDate(); + return { + fechaInicio: `${y}-${String(m).padStart(2, '0')}-01`, + fechaFin: `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`, + año: y, + mes: m, + }; +} + +function parseConciliacion(req: Request): boolean { + return req.query.conciliacion === 'true' || req.query.conciliacion === '1'; +} + +export async function getKpis(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const defaults = getDefaultRange(); + const fechaInicio = (req.query.fechaInicio as string) || defaults.fechaInicio; + const fechaFin = (req.query.fechaFin as string) || defaults.fechaFin; + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + + const tenantId = req.viewingTenantId || req.user!.tenantId; + const kpis = await dashboardService.getKpis(req.tenantPool, fechaInicio, fechaFin, tenantId, conciliacion, contribuyenteId); + res.json(kpis); + } catch (error) { + next(error); + } +} + +export async function getIngresosEgresos(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const año = parseInt(req.query.año as string) || new Date().getFullYear(); + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + const tenantId = req.viewingTenantId || req.user!.tenantId; + + const data = await dashboardService.getIngresosEgresos(req.tenantPool, año, tenantId, conciliacion, contribuyenteId); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getRegimenesDelPeriodo(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const defaults = getDefaultRange(); + const fechaInicio = (req.query.fechaInicio as string) || defaults.fechaInicio; + const fechaFin = (req.query.fechaFin as string) || defaults.fechaFin; + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + + const tenantId = req.viewingTenantId || req.user?.tenantId; + const regimenes = await dashboardService.getRegimenesDelPeriodo(req.tenantPool, fechaInicio, fechaFin, conciliacion, contribuyenteId, tenantId); + res.json(regimenes); + } catch (error) { + next(error); + } +} + +export async function getAlertas(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const limit = parseInt(req.query.limit as string) || 5; + const tenantId = req.viewingTenantId || req.user!.tenantId; + const contribuyenteId = (req.query.contribuyenteId as string) || null; + + // Combinar alertas persistidas (manuales, filtered by role) + automáticas (calculadas) + const [manuales, automaticas] = await Promise.all([ + getAlertasManualesPendientes(req.tenantPool, contribuyenteId, req.user!.userId, req.user!.role), + generarAlertasAutomaticas(req.tenantPool, tenantId, contribuyenteId), + ]); + + // Unir, ordenar por prioridad, y limitar + const prioridadOrden: Record = { alta: 1, media: 2, baja: 3 }; + const alertas = [...automaticas, ...manuales] + .sort((a, b) => (prioridadOrden[a.prioridad] || 3) - (prioridadOrden[b.prioridad] || 3)) + .slice(0, limit); + + res.json(alertas); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/despacho-audit.controller.ts b/apps/api/src/controllers/despacho-audit.controller.ts new file mode 100644 index 0000000..cc72dc7 --- /dev/null +++ b/apps/api/src/controllers/despacho-audit.controller.ts @@ -0,0 +1,67 @@ +import type { Request, Response, NextFunction } from 'express'; +import { prisma } from '../config/database.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +export async function getDespachoAuditLog(req: Request, res: Response, next: NextFunction) { + try { + if (!req.user) return next(new AppError(401, 'No autenticado')); + + const tenantId = req.viewingTenantId || req.user.tenantId; + + // Only owner or cfo can see audit log of their despacho + if (req.user.role !== 'owner' && req.user.role !== 'cfo') { + return next(new AppError(403, 'Solo el dueño puede ver el registro de accesos')); + } + + const from = req.query.from + ? new Date(req.query.from as string) + : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); + const to = req.query.to ? new Date(req.query.to as string) : new Date(); + const limit = Math.min(Number(req.query.limit) || 50, 200); + + const logs = await prisma.auditLog.findMany({ + where: { + tenantId, + action: { startsWith: 'admin.' }, + createdAt: { gte: from, lte: to }, + }, + orderBy: { createdAt: 'desc' }, + take: limit, + }); + + // Enrich with admin user info + const userIds = [...new Set(logs.filter(l => l.userId).map(l => l.userId!))]; + const users = + userIds.length > 0 + ? await prisma.user.findMany({ + where: { id: { in: userIds } }, + select: { id: true, nombre: true, email: true }, + }) + : []; + const userMap = new Map(users.map(u => [u.id, u])); + + const enriched = logs.map(log => ({ + id: log.id, + action: log.action, + timestamp: log.createdAt.toISOString(), + admin: log.userId + ? { + nombre: userMap.get(log.userId)?.nombre ?? 'Desconocido', + email: userMap.get(log.userId)?.email ?? '', + } + : null, + motivo: (log.metadata as any)?.motivo ?? null, + ip: (log.metadata as any)?.ip ?? null, + details: log.metadata, + })); + + return res.json({ + data: enriched, + total: enriched.length, + from: from.toISOString(), + to: to.toISOString(), + }); + } catch (err) { + return next(err); + } +} diff --git a/apps/api/src/controllers/despacho-stats.controller.ts b/apps/api/src/controllers/despacho-stats.controller.ts new file mode 100644 index 0000000..24c18e2 --- /dev/null +++ b/apps/api/src/controllers/despacho-stats.controller.ts @@ -0,0 +1,67 @@ +import type { Request, Response, NextFunction } from 'express'; +import { AppError } from '../middlewares/error.middleware.js'; +import * as despachoService from '../services/despacho-stats.service.js'; + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +const ROLES_OWNER = new Set(['owner', 'cfo']); +const ROLES_SUPERVISORY = new Set(['owner', 'cfo', 'supervisor']); +const ROLES_ASIGNADOS = new Set(['owner', 'cfo', 'supervisor', 'auxiliar']); + +export async function getContribuyentesStats(req: Request, res: Response, next: NextFunction) { + try { + if (!ROLES_OWNER.has(req.user!.role)) { + throw new AppError(403, 'Solo owner puede ver estas métricas'); + } + const tenantId = effectiveTenantId(req); + const año = req.query.año ? parseInt(String(req.query.año), 10) : undefined; + const mes = req.query.mes ? parseInt(String(req.query.mes), 10) : undefined; + const stats = await despachoService.getContribuyentesStats(req.tenantPool!, tenantId, año, mes); + res.json(stats); + } catch (error) { + next(error); + } +} + +export async function getMisAsignados(req: Request, res: Response, next: NextFunction) { + try { + if (!ROLES_ASIGNADOS.has(req.user!.role)) { + throw new AppError(403, 'No tienes contribuyentes asignados'); + } + const año = req.query.año ? parseInt(String(req.query.año), 10) : undefined; + const mes = req.query.mes ? parseInt(String(req.query.mes), 10) : undefined; + const data = await despachoService.getMisAsignados( + req.tenantPool!, + req.user!.userId, + req.user!.role, + año, + mes, + ); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getEquipoStats(req: Request, res: Response, next: NextFunction) { + try { + if (!ROLES_SUPERVISORY.has(req.user!.role)) { + throw new AppError(403, 'Solo owner y supervisor pueden ver al equipo'); + } + const año = req.query.año ? parseInt(String(req.query.año), 10) : undefined; + const mes = req.query.mes ? parseInt(String(req.query.mes), 10) : undefined; + const data = await despachoService.getEquipoStats( + req.tenantPool!, + req.user!.userId, + req.user!.role, + effectiveTenantId(req), + año, + mes, + ); + res.json(data); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/despacho.controller.ts b/apps/api/src/controllers/despacho.controller.ts new file mode 100644 index 0000000..96ce909 --- /dev/null +++ b/apps/api/src/controllers/despacho.controller.ts @@ -0,0 +1,98 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { signupDespacho } from '../services/despacho.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { prisma } from '../config/database.js'; + +const signupSchema = z.object({ + despacho: z.object({ + nombre: z.string().min(2, 'Nombre del despacho requerido'), + regimenFiscal: z.string().optional(), + codigoPostal: z.string().regex(/^\d{5}$/, 'Código postal inválido').optional(), + verticalProfile: z.enum(['CONTABLE', 'JURIDICO', 'ARQUITECTURA']), + plan: z.enum(['trial', 'mi_empresa', 'mi_empresa_plus', 'business_control', 'business_cloud']).optional().default('trial'), + // Solo aplica a mi_empresa y mi_empresa_plus (los otros pagados son + // anuales fijos). Default annual sesga el cash-flow del negocio. + frequency: z.enum(['monthly', 'annual']).optional().default('annual'), + }), + owner: z.object({ + nombre: z.string().min(2, 'Nombre del owner requerido'), + email: z.string().email('Email inválido'), + password: z.string().min(10, 'La contraseña debe tener al menos 10 caracteres'), + }), +}); + +export async function getMyPlan(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = req.user!.tenantId; + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { dbMode: true, trialEndsAt: true, verticalProfile: true, plan: true }, + }); + + if (!tenant) { + return next(new AppError(404, 'Tenant no encontrado')); + } + + const now = new Date(); + const isTrialActive = tenant.trialEndsAt ? tenant.trialEndsAt > now : false; + + // Mapea según trialEndsAt + tenant.plan (no dbMode). dbMode era proxy + // antes de la introducción de Mi Empresa / Mi Empresa+ — para esos + // planes, dbMode también es MANAGED y reportar `business_cloud` daba + // mapeo equivocado. tenant.plan es la fuente de verdad post-migración + // 20260426073942 (que añadió mi_empresa y mi_empresa_plus al enum). + let currentPlan: string; + if (isTrialActive) { + currentPlan = 'trial'; + } else { + currentPlan = String(tenant.plan); + } + + // Estado de suscripción activa (si hay) — alimenta la UI con el monto + // recurrente actual, fecha de próxima renovación y si el primer pago + // (cuando aplica dualidad firstYear) ya fue completado. + const subscription = await prisma.subscription.findFirst({ + where: { tenantId, status: { in: ['authorized', 'pending', 'paused', 'trial'] } }, + orderBy: { createdAt: 'desc' }, + select: { + status: true, amount: true, plan: true, + currentPeriodStart: true, currentPeriodEnd: true, + }, + }); + + return res.json({ + plan: currentPlan, + dbMode: tenant.dbMode, + trialEndsAt: tenant.trialEndsAt?.toISOString() ?? null, + isTrialActive, + subscription: subscription + ? { + status: subscription.status, + plan: subscription.plan, + amount: Number(subscription.amount), + currentPeriodStart: subscription.currentPeriodStart?.toISOString() ?? null, + currentPeriodEnd: subscription.currentPeriodEnd?.toISOString() ?? null, + } + : null, + }); + } catch (error) { + return next(error); + } +} + +export async function signup(req: Request, res: Response, next: NextFunction) { + try { + const data = signupSchema.parse(req.body); + const result = await signupDespacho(data); + return res.status(201).json(result); + } catch (error: any) { + if (error instanceof z.ZodError) { + return next(new AppError(400, error.errors[0].message)); + } + if (error.message?.includes('Ya existe')) { + return next(new AppError(409, error.message)); + } + return next(error); + } +} diff --git a/apps/api/src/controllers/documentos.controller.ts b/apps/api/src/controllers/documentos.controller.ts new file mode 100644 index 0000000..2fea881 --- /dev/null +++ b/apps/api/src/controllers/documentos.controller.ts @@ -0,0 +1,333 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { getOpiniones, getOpinionPdf, consultarOpinion, consultarOpinionContribuyente } from '../services/opinion-cumplimiento.service.js'; +import * as declaracionesService from '../services/declaraciones.service.js'; +import * as constanciaService from '../services/constancia.service.js'; +import * as extrasService from '../services/documentos-extras.service.js'; +import { notifyDocumentoSubido } from '../services/notify-upload.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +const MESES = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +export async function listarOpiniones(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + let rfc: string | undefined; + if (contribuyenteId) { + const { rows } = await req.tenantPool!.query( + 'SELECT rfc FROM contribuyentes WHERE entidad_id = $1', + [contribuyenteId], + ); + rfc = rows[0]?.rfc; + } + const opiniones = await getOpiniones(req.tenantPool!, 5, rfc); + res.json(opiniones); + } catch (error) { + next(error); + } +} + +export async function descargarPdf(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return res.status(400).json({ error: 'ID inválido' }); + + const pdf = await getOpinionPdf(req.tenantPool!, id); + if (!pdf) return res.status(404).json({ error: 'Opinión no encontrada' }); + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="opinion_cumplimiento_${id}.pdf"`); + res.send(pdf); + } catch (error) { + next(error); + } +} + +export async function consultarManual(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = effectiveTenantId(req); + const contribuyenteId = req.query.contribuyenteId as string | undefined; + + let opinion; + if (contribuyenteId) { + opinion = await consultarOpinionContribuyente(req.tenantPool!, contribuyenteId); + } else { + opinion = await consultarOpinion(tenantId); + } + res.json(opinion); + } catch (error: any) { + if (error.message?.includes('FIEL')) { + return res.status(400).json({ error: error.message }); + } + next(error); + } +} + +// ============================================================================ +// Declaraciones provisionales +// ============================================================================ + +const ROLES_UPLOAD = ['owner', 'cfo', 'contador', 'auxiliar']; + +function canUpload(req: Request): boolean { + return ROLES_UPLOAD.includes(req.user!.role); +} + +const createDeclaracionSchema = z.object({ + año: z.number().int().min(2020).max(2100), + mes: z.number().int().min(1).max(12), + tipo: z.enum(['normal', 'complementaria']), + periodicidad: z.enum(['mensual', 'bimestral', 'trimestral', 'semestral', 'anual']).optional(), + impuestos: z.array(z.enum(['IVA', 'ISR', 'IEPS', 'SUELDOS', 'DIOT', 'OTRO'])).min(1, 'Selecciona al menos un impuesto'), + montoPago: z.number().min(0).optional(), + pdfBase64: z.string().min(100), + pdfFilename: z.string().min(1).max(255), + ligaPagoBase64: z.string().min(100).optional(), + ligaPagoFilename: z.string().min(1).max(255).optional(), + notas: z.string().max(2000).optional(), +}).refine( + d => !d.ligaPagoBase64 || !!d.ligaPagoFilename, + { message: 'Si incluyes liga de pago, también debes mandar su nombre de archivo', path: ['ligaPagoFilename'] }, +); + +export async function listarDeclaraciones(req: Request, res: Response, next: NextFunction) { + try { + const fechaDesde = req.query.fechaDesde as string | undefined; + const fechaHasta = req.query.fechaHasta as string | undefined; + const contribuyenteId = typeof req.query.contribuyenteId === 'string' && req.query.contribuyenteId + ? req.query.contribuyenteId + : null; + const data = await declaracionesService.listDeclaraciones(req.tenantPool!, fechaDesde, fechaHasta, contribuyenteId); + res.json(data); + } catch (error) { next(error); } +} + +export async function crearDeclaracion(req: Request, res: Response, next: NextFunction) { + try { + if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para subir declaraciones' }); + const data = createDeclaracionSchema.parse(req.body); + const contribuyenteId = req.body.contribuyenteId as string | undefined; + const result = await declaracionesService.createDeclaracion(req.tenantPool!, { + ...data, + creadoPor: req.user!.email, + creadoPorUserId: req.user!.userId, + contribuyenteId, + }); + + // Notificación fire-and-forget a owners del despacho + supervisor del RFC. + // No bloquea la respuesta ni falla la creación si SMTP no está configurado. + notifyDocumentoSubido({ + pool: req.tenantPool!, + tenantId: req.user!.tenantId, + contribuyenteId: contribuyenteId ?? null, + subidoPor: req.user!.email, + kind: 'declaracion', + declaracion: { + periodo: `${MESES[data.mes - 1]} ${data.año}`, + tipo: data.tipo, + impuestos: data.impuestos as string[], + montoPago: data.montoPago ?? null, + }, + }).catch((err: any) => console.error('[notifyDocumentoSubido declaracion]', err?.message || err)); + + res.status(201).json(result); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + if (error?.message?.includes('Ya existe') || error?.message?.includes('normal')) { + return next(new AppError(400, error.message)); + } + next(error); + } +} + +const comprobantePagoSchema = z.object({ + pdfBase64: z.string().min(100), + pdfFilename: z.string().min(1).max(255), +}); + +export async function subirComprobantePago(req: Request, res: Response, next: NextFunction) { + try { + if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para subir comprobantes' }); + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return next(new AppError(400, 'id inválido')); + const data = comprobantePagoSchema.parse(req.body); + const result = await declaracionesService.uploadComprobantePago(req.tenantPool!, id, { + ...data, + uploadedByUserId: req.user!.userId, + }); + res.json(result); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + if (error?.message?.includes('no encontrada')) { + return next(new AppError(404, error.message)); + } + next(error); + } +} + +export async function descargarDeclaracionPdf(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return next(new AppError(400, 'id inválido')); + const v = req.params.variant; + const variant: 'declaracion' | 'liga' | 'pago' = v === 'pago' ? 'pago' : v === 'liga' ? 'liga' : 'declaracion'; + const pdf = await declaracionesService.getDeclaracionPdf(req.tenantPool!, id, variant); + if (!pdf) return res.status(404).json({ message: 'PDF no encontrado' }); + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${pdf.filename}"`); + res.send(pdf.buffer); + } catch (error) { next(error); } +} + +// ============================================================================ +// Constancia de Situación Fiscal +// ============================================================================ + +export async function listarConstancias(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + let rfc: string | undefined; + if (contribuyenteId) { + const { rows } = await req.tenantPool!.query( + 'SELECT rfc FROM contribuyentes WHERE entidad_id = $1', + [contribuyenteId], + ); + rfc = rows[0]?.rfc; + } + const data = await constanciaService.listConstancias(req.tenantPool!, 12, rfc); + res.json(data); + } catch (error) { next(error); } +} + +export async function descargarConstanciaPdf(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return next(new AppError(400, 'id inválido')); + const pdf = await constanciaService.getConstanciaPdf(req.tenantPool!, id); + if (!pdf) return res.status(404).json({ message: 'Constancia no encontrada' }); + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="constancia_${id}.pdf"`); + res.send(pdf); + } catch (error) { next(error); } +} + +export async function consultarConstanciaManual(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = effectiveTenantId(req); + const contribuyenteId = req.query.contribuyenteId as string | undefined; + + let constancia; + if (contribuyenteId) { + constancia = await constanciaService.consultarConstanciaContribuyente(req.tenantPool!, contribuyenteId); + } else { + constancia = await constanciaService.consultarConstancia(tenantId); + } + res.json(constancia); + } catch (error: any) { + if (error.message?.includes('FIEL')) return res.status(400).json({ error: error.message }); + next(error); + } +} + +export async function eliminarDeclaracion(req: Request, res: Response, next: NextFunction) { + try { + if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para eliminar declaraciones' }); + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return next(new AppError(400, 'id inválido')); + await declaracionesService.deleteDeclaracion(req.tenantPool!, id); + res.status(204).send(); + } catch (error: any) { + if (error?.message?.includes('no encontrada')) { + return next(new AppError(404, error.message)); + } + next(error); + } +} + +// ============================================================================ +// Documentos Extras — PDFs libres (acuses, contratos, poderes, estados, etc.) +// ============================================================================ + +const createExtraSchema = z.object({ + nombre: z.string().min(1, 'Nombre requerido').max(255), + descripcion: z.string().max(2000).optional(), + categoria: z.string().max(100).optional(), + pdfBase64: z.string().min(100, 'PDF requerido'), + pdfFilename: z.string().min(1).max(255), +}); + +export async function listarExtras(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const categoria = req.query.categoria as string | undefined; + const data = await extrasService.listExtras(req.tenantPool!, contribuyenteId, categoria); + res.json(data); + } catch (error) { next(error); } +} + +export async function crearExtra(req: Request, res: Response, next: NextFunction) { + try { + if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para subir documentos' }); + const data = createExtraSchema.parse(req.body); + const contribuyenteId = req.body.contribuyenteId as string | undefined; + const result = await extrasService.createExtra(req.tenantPool!, { + ...data, + contribuyenteId: contribuyenteId ?? null, + subidoPor: req.user!.email, + }); + + // Notificación fire-and-forget a owners del despacho + supervisor del RFC. + notifyDocumentoSubido({ + pool: req.tenantPool!, + tenantId: req.user!.tenantId, + contribuyenteId: contribuyenteId ?? null, + subidoPor: req.user!.email, + kind: 'extra', + extra: { + nombre: data.nombre, + descripcion: data.descripcion ?? null, + categoria: data.categoria ?? null, + }, + }).catch((err: any) => console.error('[notifyDocumentoSubido extra]', err?.message || err)); + + res.status(201).json(result); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function descargarExtraPdf(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return next(new AppError(400, 'id inválido')); + const pdf = await extrasService.getExtraPdf(req.tenantPool!, id); + if (!pdf) return next(new AppError(404, 'Documento no encontrado')); + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${pdf.filename}"`); + res.send(pdf.buffer); + } catch (error) { next(error); } +} + +export async function eliminarExtra(req: Request, res: Response, next: NextFunction) { + try { + if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para eliminar documentos' }); + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return next(new AppError(400, 'id inválido')); + const ok = await extrasService.deleteExtra(req.tenantPool!, id); + if (!ok) return next(new AppError(404, 'Documento no encontrado')); + res.status(204).send(); + } catch (error) { next(error); } +} + +export async function listarCategoriasExtras(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const data = await extrasService.listCategorias(req.tenantPool!, contribuyenteId); + res.json(data); + } catch (error) { next(error); } +} diff --git a/apps/api/src/controllers/export.controller.ts b/apps/api/src/controllers/export.controller.ts new file mode 100644 index 0000000..d4e7a60 --- /dev/null +++ b/apps/api/src/controllers/export.controller.ts @@ -0,0 +1,42 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as exportService from '../services/export.service.js'; + +export async function exportCfdis(req: Request, res: Response, next: NextFunction) { + try { + const { tipo, estado, fechaInicio, fechaFin } = req.query; + const buffer = await exportService.exportCfdisToExcel(req.tenantPool!, { + tipo: tipo as string, + estado: estado as string, + fechaInicio: fechaInicio as string, + fechaFin: fechaFin as string, + }); + + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=cfdis-${Date.now()}.xlsx`); + res.send(buffer); + } catch (error) { + next(error); + } +} + +export async function exportReporte(req: Request, res: Response, next: NextFunction) { + try { + const { tipo, fechaInicio, fechaFin } = req.query; + const now = new Date(); + const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; + const fin = (fechaFin as string) || now.toISOString().split('T')[0]; + + const buffer = await exportService.exportReporteToExcel( + req.tenantPool!, + tipo as 'estado-resultados' | 'flujo-efectivo', + inicio, + fin + ); + + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=${tipo}-${Date.now()}.xlsx`); + res.send(buffer); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/facturacion.controller.ts b/apps/api/src/controllers/facturacion.controller.ts new file mode 100644 index 0000000..eef2503 --- /dev/null +++ b/apps/api/src/controllers/facturacion.controller.ts @@ -0,0 +1,789 @@ +import type { Request, Response, NextFunction } from 'express'; +import type { Pool } from 'pg'; +import { z } from 'zod'; +import * as facturapiService from '../services/facturapi.service.js'; +import { + createInvoiceContribuyente, + cancelInvoiceContribuyente, + downloadPdfContribuyente, + downloadXmlContribuyente, + sendInvoiceByEmailContribuyente, +} from '../services/contribuyente-facturapi.service.js'; +import { parseXml } from '../services/sat/sat-parser.service.js'; +import * as tenantsService from '../services/tenants.service.js'; +import { prisma } from '../config/database.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { hasPlatformRole } from '../utils/platform-admin.js'; +import { auditFromReq } from '../utils/audit.js'; + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +/** + * Detecta si un mensaje de error del SAT (propagado por Facturapi) indica + * que el CSD aún no está en la Lista de Contribuyentes Obligados (LCO). + * El SAT tarda 24-72h en propagar un CSD nuevo; durante esa ventana todo + * intento de emisión falla. Cuando se detecta este patrón se marca la + * org con `last_lco_rejection_at` para que el frontend muestre un banner. + */ +function isLcoRejection(errorMessage: string): boolean { + if (!errorMessage) return false; + const msg = errorMessage.toLowerCase(); + return ( + /no se encontr.*rfc.*lco/.test(msg) || + /rfc.*no.*registrado.*lco/.test(msg) || + /lista.*contribuyentes.*obligados/.test(msg) || + /csd.*no.*registrad/.test(msg) || + msg.includes('lco') + ); +} + +/** + * Registra el timestamp del rechazo LCO en la fila correspondiente de + * `facturapi_orgs`. Fire-and-forget: un fallo aquí no bloquea la + * propagación del error al frontend. + */ +async function markLcoRejection( + pool: import('pg').Pool, + contribuyenteId: string | undefined, +): Promise { + try { + if (contribuyenteId) { + await pool.query( + `UPDATE facturapi_orgs SET last_lco_rejection_at = NOW() WHERE contribuyente_id = $1`, + [contribuyenteId], + ); + } + // Nota: Horux360 single-tenant usaría `tenants.facturapi_org_id` en + // BD central; en el fork multi-contribuyente solo marcamos la fila + // por-contribuyente. Si el user emite desde el org del tenant (sin + // contribuyenteId), el banner no aplicaría aquí. + } catch (e: any) { + console.error('[facturacion.markLcoRejection] falló UPDATE:', e?.message || e); + } +} + +// ── Organización ── + +export async function getOrgStatus(req: Request, res: Response, next: NextFunction) { + try { + const status = await facturapiService.getOrganizationStatus(effectiveTenantId(req)); + res.json(status); + } catch (error) { next(error); } +} + +export async function createOrg(req: Request, res: Response, next: NextFunction) { + try { + const result = await facturapiService.createOrganization(effectiveTenantId(req)); + res.status(201).json(result); + } catch (error) { next(error); } +} + +// ── CSD ── + +export async function uploadCsd(req: Request, res: Response, next: NextFunction) { + try { + const { cerFile, keyFile, password } = req.body; + if (!cerFile || !keyFile || !password) { + return res.status(400).json({ message: 'cerFile, keyFile y password son requeridos' }); + } + const result = await facturapiService.uploadCsd(effectiveTenantId(req), cerFile, keyFile, password); + if (!result.success) return res.status(400).json({ message: result.message }); + res.json(result); + } catch (error) { next(error); } +} + +// ── Emisión ── + +export async function emitir(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = effectiveTenantId(req); + const contribuyenteId = req.body.contribuyenteId as string | undefined; + + // ── Validar CFDIs relacionados antes de consumir timbre ── + // En Live, SAT rechaza si el UUID relacionado no existe, está cancelado, + // o el rfc_receptor no coincide con el customer.taxId del CFDI nuevo. + // Catch temprano con error legible en vez de un 500 oscuro de Facturapi. + const relatedDocs: Array<{ relationship: string; uuids: string[] }> = req.body.relatedDocuments || []; + const customerRfc = req.body.customer?.taxId?.toUpperCase()?.trim(); + if (relatedDocs.length > 0 && customerRfc && req.tenantPool) { + const allUuids = relatedDocs + .flatMap(r => r.uuids || []) + .filter(u => typeof u === 'string' && u.trim() !== ''); + for (const uuid of allUuids) { + const { rows } = await req.tenantPool.query( + `SELECT rfc_receptor, status FROM cfdis WHERE LOWER(uuid) = LOWER($1) LIMIT 1`, + [uuid.trim()], + ); + if (rows.length === 0) { + throw new AppError(400, `El CFDI relacionado con UUID ${uuid} no existe en el sistema.`); + } + const rel = rows[0]; + if (rel.status === 'Cancelado' || rel.status === '0') { + throw new AppError(400, `El CFDI relacionado con UUID ${uuid} está cancelado.`); + } + const rfcReceptorRel = (rel.rfc_receptor || '').toUpperCase().trim(); + if (rfcReceptorRel !== customerRfc) { + throw new AppError( + 400, + `El CFDI relacionado con UUID ${uuid} no corresponde al RFC del receptor de esta factura. ` + + `RFC esperado: ${customerRfc}. RFC del receptor del CFDI relacionado: ${rfcReceptorRel}.`, + ); + } + } + } + + // Reservar timbre — si falla emisión en Facturapi, revertimos abajo + const consumedTimbre = await facturapiService.consumeTimbre(tenantId); + + // Emitir factura en Facturapi + // Si hay contribuyenteId, usar la org Facturapi del contribuyente (tenant BD). + // Si no, usar la org del tenant (BD central). + let invoice; + try { + if (contribuyenteId) { + invoice = await createInvoiceContribuyente(req.tenantPool!, contribuyenteId, req.body); + } else { + invoice = await facturapiService.createInvoice(tenantId, req.body); + } + } catch (err: any) { + // SAT nunca selló → revertir el timbre reservado (fire-and-forget; no bloquear la respuesta + // de error si el refund falla, solo loggear la inconsistencia) + facturapiService.refundTimbre(tenantId, consumedTimbre).catch(refundErr => { + console.error('[facturacion.emitir] Falló refund de timbre tras rechazo Facturapi:', { + tenantId, + consumedTimbre, + refundError: refundErr?.message || String(refundErr), + }); + }); + // Loggea el payload que causó el rechazo para diagnóstico server-side + console.error('[facturacion.emitir] Rechazo al crear factura:', { + tenantId, + contribuyenteId: contribuyenteId || null, + type: req.body?.type, + items: req.body?.items?.map((it: any) => ({ + description: it.description, + taxes: it.taxes, + })), + error: err?.message || String(err), + }); + // Detectar rechazo por CSD aún no propagado a la LCO y marcar la org + // para que el frontend muestre banner informativo durante 24h. + if (isLcoRejection(err?.message || '')) { + await markLcoRejection(req.tenantPool!, contribuyenteId); + } + // Propaga el mensaje real (Facturapi suele explicar la validación) + throw new AppError(400, err?.message || 'Error al emitir factura'); + } + + // Guardar en tabla cfdis del tenant. + // El response de `invoices.create` de Facturapi NO incluye `issuer`/`subtotal`/`taxes` + // como campos top-level (usa `issuer_info` y los impuestos viven dentro de `items[*].product.taxes`). + // La forma más fiable y consistente con el sync SAT es descargar el XML timbrado y + // reutilizar el mismo parser que ya procesa los CFDIs descargados del SAT. + const pool = req.tenantPool!; + const xmlBuffer = contribuyenteId + ? await downloadXmlContribuyente(pool, contribuyenteId, invoice.id) + : await facturapiService.downloadXml(tenantId, invoice.id); + const xmlString = xmlBuffer.toString('utf-8'); + const parsed = parseXml(xmlString, 'emitidos'); + if (!parsed) { + throw new AppError(500, `Factura ${invoice.uuid} emitida en Facturapi pero el XML no pudo parsearse`); + } + + const fecha = parsed.fechaEmision; + const year = String(fecha.getFullYear()); + const month = String(fecha.getMonth() + 1).padStart(2, '0'); + + // Upsert RFCs desde datos del XML (fuente autoritativa — igual al sync SAT) + const { rows: [emisorRow] } = await pool.query( + `INSERT INTO rfcs (rfc, razon_social, regimen_fiscal) VALUES ($1, $2, $3) + ON CONFLICT (rfc) DO UPDATE SET + razon_social = COALESCE(NULLIF($2, ''), rfcs.razon_social), + regimen_fiscal = CASE WHEN $3 IS NOT NULL AND $3 != '' THEN $3 ELSE rfcs.regimen_fiscal END + RETURNING id`, + [parsed.rfcEmisor, parsed.nombreEmisor || null, parsed.regimenFiscalEmisor || null], + ); + const { rows: [receptorRow] } = await pool.query( + `INSERT INTO rfcs (rfc, razon_social, regimen_fiscal, codigo_postal) VALUES ($1, $2, $3, $4) + ON CONFLICT (rfc) DO UPDATE SET + razon_social = COALESCE(NULLIF($2, ''), rfcs.razon_social), + regimen_fiscal = CASE WHEN $3 IS NOT NULL AND $3 != '' THEN $3 ELSE rfcs.regimen_fiscal END, + codigo_postal = CASE WHEN $4 IS NOT NULL AND $4 != '' THEN $4 ELSE rfcs.codigo_postal END + RETURNING id`, + [parsed.rfcReceptor, parsed.nombreReceptor || null, parsed.regimenFiscalReceptor || null, req.body.customer?.zip || null], + ); + + // Para CFDIs tipo P (complemento de pago) parseamos `fechaPagoP`. SAT + // permite múltiples pagos por complemento — el parser concatena las fechas + // con '|'; aquí tomamos la primera (suficiente para el cálculo fiscal, + // donde fecha_pago_p drives el período de devengo). + const fechaPagoP = parsed.fechaPagoP + ? new Date(String(parsed.fechaPagoP).split('|')[0]) + : null; + + await pool.query(` + INSERT INTO cfdis ( + year, month, type, uuid, serie, folio, status, fecha_emision, fecha_cert_sat, + rfc_emisor_id, rfc_emisor, nombre_emisor, regimen_fiscal_emisor, + rfc_receptor_id, rfc_receptor, nombre_receptor, regimen_fiscal_receptor, + subtotal, subtotal_mxn, total, total_mxn, + moneda, tipo_comprobante, metodo_pago, forma_pago, uso_cfdi, + iva_traslado, iva_traslado_mxn, + iva_retencion, iva_retencion_mxn, + monto_pago, monto_pago_mxn, + fecha_pago_p, + iva_traslado_pago, iva_traslado_pago_mxn, + iva_retencion_pago, iva_retencion_pago_mxn, + ieps_traslado_pago, ieps_traslado_pago_mxn, + source, facturapi_id, + contribuyente_id, xml_original + ) VALUES ( + $1, $2, 'EMITIDO', $3, $4, $5, 'Vigente', $6, $7, + $8, $9, $10, $11, + $12, $13, $14, $15, + $16, $16, $17, $17, + $18, $19, $20, $21, $22, + $23, $23, + $24, $24, + $25, $25, + $26, + $27, $27, + $28, $28, + $29, $29, + 'facturapi', $30, + $31, $32 + ) + `, [ + year, month, parsed.uuid, parsed.serie, parsed.folio, fecha, parsed.fechaCertSat, + emisorRow.id, parsed.rfcEmisor, parsed.nombreEmisor, parsed.regimenFiscalEmisor, + receptorRow.id, parsed.rfcReceptor, parsed.nombreReceptor, parsed.regimenFiscalReceptor, + parsed.subtotal, parsed.total, + parsed.moneda, parsed.tipoComprobante, parsed.metodoPago, parsed.formaPago, parsed.usoCfdi, + parsed.ivaTraslado, + parsed.ivaRetencion, + parsed.montoPago, + fechaPagoP, + parsed.ivaTrasladoPago, + parsed.ivaRetencionPago, + parsed.iepsTrasladoPago, + invoice.id, + contribuyenteId ?? null, xmlString, + ]); + + // Enviar por email si el receptor tiene email — ruteado a la org correcta + const customerEmail = req.body.customer?.email; + if (customerEmail) { + const sendPromise = contribuyenteId + ? sendInvoiceByEmailContribuyente(req.tenantPool!, contribuyenteId, invoice.id, customerEmail) + : facturapiService.sendInvoiceByEmail(tenantId, invoice.id, customerEmail); + sendPromise.catch(err => console.error('[Facturapi] Error enviando email:', err.message)); + } + + res.status(201).json({ + id: invoice.id, + uuid: invoice.uuid, + total: invoice.total, + status: invoice.status, + }); + } catch (error: any) { + // Los errores de emisión ya hacen refund dentro del inner catch. + // Aquí solo propagamos — incluye errores del INSERT post-emisión (CFDI ya sellado, + // no refund) y errores de validación de timbre (ocurrieron antes del consume). + next(error); + } +} + +// Estado LCO: si hubo un rechazo del SAT por CSD no propagado en las últimas 24h, +// el frontend muestra un banner informativo en la pantalla de emisión. +export async function getLcoStatus(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string | undefined; + if (!contribuyenteId) { + return res.json({ hasRecentLcoRejection: false, rejectedAt: null }); + } + + const { rows } = await req.tenantPool!.query<{ last_lco_rejection_at: Date | null }>( + `SELECT last_lco_rejection_at FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true`, + [contribuyenteId], + ); + + const rejectedAt = rows[0]?.last_lco_rejection_at || null; + const hasRecentLcoRejection = + rejectedAt !== null && Date.now() - new Date(rejectedAt).getTime() < 24 * 60 * 60 * 1000; + + res.json({ hasRecentLcoRejection, rejectedAt }); + } catch (error) { + next(error); + } +} + +// ── Cancelación ── + +export async function cancelar(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = effectiveTenantId(req); + const { uuid } = req.params; + const { motive, substitution } = req.body; + + const pool = req.tenantPool!; + const { rows } = await pool.query( + `SELECT facturapi_id, contribuyente_id FROM cfdis WHERE uuid = $1 AND source = 'facturapi'`, + [uuid] + ); + + if (rows.length === 0 || !rows[0].facturapi_id) { + return res.status(404).json({ message: 'CFDI no encontrado o no fue emitido por Facturapi' }); + } + + const facturapiId = rows[0].facturapi_id; + const cfdiContribuyenteId = rows[0].contribuyente_id as string | null; + + const result = cfdiContribuyenteId + ? await cancelInvoiceContribuyente(pool, cfdiContribuyenteId, facturapiId, motive || '02', substitution) + : await facturapiService.cancelInvoice(tenantId, facturapiId, motive || '02', substitution); + + // Capturamos la fecha del CFDI antes del UPDATE para saber qué mes marcar + // como invalidado (la cancelación afecta las métricas del mes del CFDI, + // no del mes actual). + const { rows: fechas } = await pool.query<{ fecha_emision: Date; fecha_pago_p: Date | null; tipo_comprobante: string }>( + `SELECT fecha_emision, fecha_pago_p, tipo_comprobante FROM cfdis WHERE uuid = $1`, + [uuid], + ); + + await pool.query( + `UPDATE cfdis SET status = 'Cancelado', fecha_cancelacion = NOW(), actualizado_en = NOW() WHERE uuid = $1`, + [uuid] + ); + + // Invalidar métricas del mes afectado (usa fecha_pago_p para P, fecha_emision para el resto) + if (cfdiContribuyenteId && fechas[0]) { + const f = fechas[0]; + const fechaContable = f.tipo_comprobante === 'P' && f.fecha_pago_p ? f.fecha_pago_p : f.fecha_emision; + const { markForInvalidation } = await import('../services/metricas.service.js'); + await markForInvalidation( + pool, + cfdiContribuyenteId, + fechaContable.getFullYear(), + fechaContable.getMonth() + 1, + 'CFDI_CANCEL', + ).catch(err => console.warn('[Cancelar] markForInvalidation falló:', err?.message || err)); + } + + res.json({ message: 'CFDI cancelado', result }); + } catch (error) { next(error); } +} + +// ── Descargas ── + +async function resolveCfdiContribuyenteId( + pool: Pool, + facturapiId: string, +): Promise { + const { rows } = await pool.query<{ contribuyente_id: string | null }>( + `SELECT contribuyente_id FROM cfdis WHERE facturapi_id = $1 LIMIT 1`, + [facturapiId], + ); + return rows[0]?.contribuyente_id ?? null; +} + +export async function downloadPdf(req: Request, res: Response, next: NextFunction) { + try { + const id = String(req.params.id); + const pool = req.tenantPool!; + const cfdiContribuyenteId = await resolveCfdiContribuyenteId(pool, id); + const buffer = cfdiContribuyenteId + ? await downloadPdfContribuyente(pool, cfdiContribuyenteId, id) + : await facturapiService.downloadPdf(effectiveTenantId(req), id); + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename=factura-${id}.pdf`); + res.send(buffer); + } catch (error) { next(error); } +} + +export async function downloadXml(req: Request, res: Response, next: NextFunction) { + try { + const id = String(req.params.id); + const pool = req.tenantPool!; + const cfdiContribuyenteId = await resolveCfdiContribuyenteId(pool, id); + const buffer = cfdiContribuyenteId + ? await downloadXmlContribuyente(pool, cfdiContribuyenteId, id) + : await facturapiService.downloadXml(effectiveTenantId(req), id); + res.setHeader('Content-Type', 'application/xml'); + res.setHeader('Content-Disposition', `attachment; filename=factura-${id}.xml`); + res.send(buffer); + } catch (error) { next(error); } +} + +// ── Timbres ── + +export async function getTimbres(req: Request, res: Response, next: NextFunction) { + try { + const status = await facturapiService.getTimbreStatus(effectiveTenantId(req)); + res.json(status); + } catch (error) { next(error); } +} + +// ── Personalización (logo, color) ── + +export async function getCustomization(req: Request, res: Response, next: NextFunction) { + try { + const data = await facturapiService.getCustomization(effectiveTenantId(req)); + res.json(data || {}); + } catch (error) { next(error); } +} + +export async function uploadLogo(req: Request, res: Response, next: NextFunction) { + try { + const { logo } = req.body; // base64 + if (!logo) return res.status(400).json({ message: 'Logo es requerido (base64)' }); + const result = await facturapiService.uploadLogo(effectiveTenantId(req), logo); + if (!result.success) return res.status(400).json({ message: result.message }); + res.json(result); + } catch (error) { next(error); } +} + +export async function updateColor(req: Request, res: Response, next: NextFunction) { + try { + const { color } = req.body; + if (!color) return res.status(400).json({ message: 'Color es requerido' }); + const result = await facturapiService.updateColor(effectiveTenantId(req), color); + if (!result.success) return res.status(400).json({ message: result.message }); + res.json(result); + } catch (error) { next(error); } +} + +// ── Datos fiscales del tenant ── + +// Schema Zod para preferencias de auto-facturación +const PreferenciasFacturacionSchema = z.object({ + factPreferencia: z.enum(['publico_general', 'mis_datos']).optional(), + factUsoCfdi: z.string().min(2).max(5).optional(), + factRegimenPreferido: z.string().max(3).nullable().optional(), +}); + +export async function getPreferenciasFacturacion(req: Request, res: Response, next: NextFunction) { + try { + const data = await tenantsService.getPreferenciasFacturacion(effectiveTenantId(req)); + res.json(data); + } catch (error) { next(error); } +} + +export async function updatePreferenciasFacturacion(req: Request, res: Response, next: NextFunction) { + try { + const parsed = PreferenciasFacturacionSchema.parse(req.body); + const data = await tenantsService.updatePreferenciasFacturacion(effectiveTenantId(req), parsed); + res.json(data); + } catch (error: any) { + if (error?.name === 'ZodError') { + return next(new AppError(400, error.errors[0].message)); + } + next(error); + } +} + +export async function getDatosFiscales(req: Request, res: Response, next: NextFunction) { + try { + const data = await tenantsService.getDatosFiscales(effectiveTenantId(req)); + res.json(data || {}); + } catch (error) { next(error); } +} + +export async function updateDatosFiscales(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') { + return res.status(403).json({ message: 'Solo el dueño puede actualizar datos fiscales' }); + } + const data = await tenantsService.updateDatosFiscales(effectiveTenantId(req), req.body); + res.json(data); + } catch (error) { next(error); } +} + +// ── Búsqueda de conceptos previos ── + +export async function searchConceptos(req: Request, res: Response, next: NextFunction) { + try { + const q = (req.query.q as string || '').trim(); + const tipo = (req.query.tipo as string || 'todos'); // emitidos, recibidos, todos + const contribuyenteId = (req.query.contribuyenteId as string || '').replace(/[^a-f0-9-]/gi, ''); + const pool = req.tenantPool!; + + let whereType = ''; + if (tipo === 'emitidos') { + whereType = `AND c.type = 'EMITIDO'`; + } else if (tipo === 'recibidos') { + whereType = `AND c.type = 'RECIBIDO' AND c.uso_cfdi = 'G01'`; + } else { + whereType = `AND (c.type = 'EMITIDO' OR (c.type = 'RECIBIDO' AND c.uso_cfdi = 'G01'))`; + } + + const whereContrib = contribuyenteId ? `AND c.contribuyente_id = '${contribuyenteId}'` : ''; + + let whereSearch = ''; + const params: any[] = []; + if (q.length >= 2) { + params.push(`%${q}%`); + whereSearch = `AND (cc.descripcion ILIKE $1 OR cc.clave_prod_serv ILIKE $1)`; + } + + const { rows } = await pool.query(` + SELECT DISTINCT ON (cc.clave_prod_serv, cc.descripcion) + cc.clave_prod_serv as "claveProdServ", + cc.descripcion, + cc.clave_unidad as "claveUnidad", + cc.unidad, + cc.valor_unitario_mxn as "valorUnitario", + cc.importe_mxn as "importe", + cc.iva_traslado_mxn as "ivaTraslado", + cc.isr_retencion_mxn as "isrRetencion", + cc.iva_retencion_mxn as "ivaRetencion", + c.type as "tipoCfdi", + c.rfc_emisor as "rfcEmisor", + c.nombre_emisor as "nombreEmisor", + c.rfc_receptor as "rfcReceptor", + c.nombre_receptor as "nombreReceptor", + c.fecha_emision as "fechaEmision" + FROM cfdi_conceptos cc + JOIN cfdis c ON cc.cfdi_id = c.id + WHERE c.status NOT IN ('Cancelado', '0') + ${whereType} + ${whereContrib} + ${whereSearch} + ORDER BY cc.clave_prod_serv, cc.descripcion, c.fecha_emision DESC + LIMIT 30 + `, params); + + res.json(rows); + } catch (error) { next(error); } +} + +// ── CFDIs PPD pendientes ── + +export async function getCfdisPpdPendientes(req: Request, res: Response, next: NextFunction) { + try { + const rfc = (req.query.rfc as string || '').trim().toUpperCase(); + if (rfc.length < 3) return res.json([]); + + const contribuyenteId = (req.query.contribuyenteId as string || '').trim(); + const pool = req.tenantPool!; + + // Buscar CFDIs emitidos PPD vigentes para este RFC receptor con saldo > 0. + // Usamos `saldo_pendiente_mxn` denormalizado (utils/saldo.ts §13) que ya + // considera pagos P + NCs no-07 + anticipos aplicados. Es la fuente de + // verdad del sistema — recalcular con subquery solo sobre pagos P + // sobreestima el saldo cuando hay NCs/anticipos. + // En multi-RFC con contribuyente activo, filtra por contribuyente_id — + // solo los PPDs emitidos por el contribuyente activo. Sin contribuyenteId, + // retorna todos los del tenant (compat con flujos sin contribuyente activo). + const params: any[] = [rfc]; + let contribFilter = ''; + if (contribuyenteId) { + params.push(contribuyenteId); + contribFilter = ` AND c.contribuyente_id = $${params.length}`; + } + const { rows } = await pool.query(` + SELECT + c.uuid, c.serie, c.folio, c.total_mxn as "totalMxn", + c.fecha_emision as "fechaEmision", + c.rfc_receptor as "rfcReceptor", + c.nombre_receptor as "nombreReceptor", + c.iva_traslado_mxn as "ivaTrasladoMxn", + c.saldo_pendiente_mxn as "saldoPendiente" + FROM cfdis c + WHERE c.type = 'EMITIDO' + AND c.metodo_pago = 'PPD' + AND c.tipo_comprobante = 'I' + AND c.status NOT IN ('Cancelado', '0') + AND c.rfc_receptor = $1${contribFilter} + AND COALESCE(c.saldo_pendiente_mxn, 0) > 0 + ORDER BY c.fecha_emision DESC + LIMIT 20 + `, params); + + res.json(rows); + } catch (error) { next(error); } +} + +// ── CFDIs relacionables ── +// Devuelve CFDIs emitidos por el contribuyente activo cuyo rfc_receptor +// coincide con el de la nueva factura. Usado por el dropdown de la sección +// "CFDIs Relacionados" en facturación tipo I y E. +// +// Filtros aplicados: +// - contribuyente_id = caller (multi-RFC: solo CFDIs del contribuyente activo) +// - rfc_receptor = rfc del receptor de la factura nueva +// - tipo_comprobante IN ('I','E') — los relacionables habituales +// - status NOT IN ('Cancelado','0') — solo vigentes (SAT rechaza relacionar cancelados) + +export async function getCfdisRelacionables(req: Request, res: Response, next: NextFunction) { + try { + const rfcReceptor = (req.query.rfcReceptor as string || '').trim().toUpperCase(); + const contribuyenteId = (req.query.contribuyenteId as string || '').trim(); + if (rfcReceptor.length < 12) return res.json([]); + if (!contribuyenteId) return res.json([]); + + const pool = req.tenantPool!; + const { rows } = await pool.query(` + SELECT + uuid, + serie, + folio, + total_mxn AS "totalMxn", + fecha_emision AS "fechaEmision", + tipo_comprobante AS "tipoComprobante", + metodo_pago AS "metodoPago" + FROM cfdis + WHERE contribuyente_id = $1 + AND rfc_receptor = $2 + AND tipo_comprobante IN ('I', 'E') + AND status NOT IN ('Cancelado', '0') + ORDER BY fecha_emision DESC + LIMIT 50 + `, [contribuyenteId, rfcReceptor]); + + res.json(rows); + } catch (error) { next(error); } +} + +// ── Búsqueda de RFCs ── + +export async function searchRfcs(req: Request, res: Response, next: NextFunction) { + try { + const q = (req.query.q as string || '').trim(); + if (q.length < 3) return res.json([]); + + const contribuyenteId = (req.query.contribuyenteId as string || '').trim(); + const pool = req.tenantPool!; + + // RFC del tenant despacho para excluirlo (no se factura a sí mismo) + const tenantId = effectiveTenantId(req); + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { rfc: true }, + }); + const tenantRfc = tenant?.rfc || ''; + + // En multi-RFC con contribuyente activo, filtrar a contrapartes con las + // que ese contribuyente ha tenido CFDIs (emisor o receptor). Sin + // contribuyenteId, retornar el catálogo completo (compat con flujos + // legacy / admin global sin contribuyente seleccionado). + let rows; + if (contribuyenteId) { + ({ rows } = await pool.query(` + SELECT DISTINCT r.id, r.rfc, + r.razon_social as "razonSocial", + r.regimen_fiscal as "regimenFiscal", + r.codigo_postal as "codigoPostal" + FROM rfcs r + WHERE r.rfc != $1 + AND (r.rfc ILIKE $2 OR r.razon_social ILIKE $2) + AND EXISTS ( + SELECT 1 FROM cfdis c + WHERE c.contribuyente_id = $3 + AND (c.rfc_emisor_id = r.id OR c.rfc_receptor_id = r.id) + ) + ORDER BY r.razon_social + LIMIT 10 + `, [tenantRfc, `%${q}%`, contribuyenteId])); + } else { + ({ rows } = await pool.query(` + SELECT id, rfc, razon_social as "razonSocial", + regimen_fiscal as "regimenFiscal", + codigo_postal as "codigoPostal" + FROM rfcs + WHERE rfc != $1 + AND (rfc ILIKE $2 OR razon_social ILIKE $2) + ORDER BY razon_social + LIMIT 10 + `, [tenantRfc, `%${q}%`])); + } + + res.json(rows); + } catch (error) { next(error); } +} + +// ── Timbres adicionales: catálogo + compra ── + +export async function getPaquetesCatalogo(req: Request, res: Response, next: NextFunction) { + try { + const catalogo = await facturapiService.listPaquetesCatalogo(); + res.json(catalogo); + } catch (error) { next(error); } +} + +const comprarPaqueteSchema = z.object({ + catalogoId: z.number().int().positive(), +}); + +// Admin global: catálogo completo incluyendo inactivos + edit +export async function getPaquetesCatalogoAdmin(req: Request, res: Response, next: NextFunction) { + try { + if (!(await hasPlatformRole(req.user!.userId, 'platform_admin'))) { + return res.status(403).json({ message: 'Solo admin global puede ver el catálogo completo' }); + } + const catalogo = await facturapiService.listAllPaquetesCatalogo(); + res.json(catalogo); + } catch (error) { next(error); } +} + +const updatePaqueteSchema = z.object({ + precio: z.number().positive().optional(), + active: z.boolean().optional(), +}); + +export async function updatePaqueteCatalogo(req: Request, res: Response, next: NextFunction) { + try { + if (!(await hasPlatformRole(req.user!.userId, 'platform_admin'))) { + return res.status(403).json({ message: 'Solo admin global puede editar el catálogo' }); + } + const id = parseInt(String(req.params.id)); + if (isNaN(id)) return next(new AppError(400, 'id inválido')); + + const data = updatePaqueteSchema.parse(req.body); + const before = await facturapiService.listAllPaquetesCatalogo().then(r => r.find(p => p.id === id)); + const updated = await facturapiService.updatePaqueteCatalogo({ id, ...data }); + + auditFromReq(req, 'timbres.catalogo_updated', { + entityType: 'TimbrePaqueteCatalogo', + entityId: String(id), + metadata: { + cantidad: updated.cantidad, + from: { precio: before?.precio, active: before?.active }, + to: { precio: updated.precio, active: updated.active }, + }, + }); + + res.json(updated); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + if (error?.message?.includes('precio') || error?.message?.includes('actualizar')) { + return next(new AppError(400, error.message)); + } + next(error); + } +} + +export async function comprarPaquete(req: Request, res: Response, next: NextFunction) { + try { + if (!['owner', 'cfo'].includes(req.user!.role)) { + return res.status(403).json({ message: 'Solo owner/cfo pueden comprar timbres adicionales' }); + } + const { catalogoId } = comprarPaqueteSchema.parse(req.body); + const result = await facturapiService.iniciarCompraPaquete({ + tenantId: effectiveTenantId(req), + catalogoId, + callerEmail: req.user!.email, + }); + res.json(result); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + // Errores de negocio esperados → 400 con mensaje para el usuario + const msg = error?.message || ''; + if (msg.includes('no disponible') || msg.includes('dueño') || msg.includes('email') || msg.includes('MercadoPago')) { + return next(new AppError(400, msg)); + } + console.error('[comprarPaquete] Error no esperado:', error); + next(error); + } +} diff --git a/apps/api/src/controllers/fiel.controller.ts b/apps/api/src/controllers/fiel.controller.ts new file mode 100644 index 0000000..a55b287 --- /dev/null +++ b/apps/api/src/controllers/fiel.controller.ts @@ -0,0 +1,136 @@ +import type { Request, Response } from 'express'; +import { uploadFiel, getFielStatus, deleteFiel } from '../services/fiel.service.js'; +import type { FielUploadRequest } from '@horux/shared'; +import type { Pool } from 'pg'; + +/** + * Crea recordatorios automáticos de vencimiento de e.firma en el calendario. + * 60 días, 30 días y 7 días antes del vencimiento. + * Elimina recordatorios previos de e.firma antes de crear nuevos. + */ +async function crearRecordatoriosEfirma( + pool: Pool, + userId: string, + validUntil: string, +): Promise { + const vencimiento = new Date(validUntil); + const PREFIJO = '[e.firma]'; + + // Eliminar recordatorios previos de e.firma para evitar duplicados al re-subir + await pool.query( + `DELETE FROM recordatorios WHERE titulo LIKE $1`, + [`${PREFIJO}%`] + ); + + const recordatorios = [ + { dias: 60, titulo: `${PREFIJO} Tu e.firma vence en 60 días` }, + { dias: 30, titulo: `${PREFIJO} Tu e.firma vence en 30 días` }, + { dias: 7, titulo: `${PREFIJO} Tu e.firma vence en 7 días — ¡Renueva pronto!` }, + ]; + + for (const { dias, titulo } of recordatorios) { + const fecha = new Date(vencimiento); + fecha.setDate(fecha.getDate() - dias); + + // Solo crear si la fecha no ha pasado + if (fecha > new Date()) { + await pool.query( + `INSERT INTO recordatorios (titulo, descripcion, fecha_limite, privado, creado_por) + VALUES ($1, $2, $3, false, $4)`, + [ + titulo, + `La e.firma (FIEL) vence el ${vencimiento.toLocaleDateString('es-MX')}. Renueva en el portal del SAT.`, + fecha.toISOString().split('T')[0], + userId, + ] + ); + } + } +} + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +/** + * Sube y configura las credenciales FIEL + */ +export async function upload(req: Request, res: Response): Promise { + try { + const tenantId = effectiveTenantId(req); + + const { cerFile, keyFile, password } = req.body as FielUploadRequest; + + if (!cerFile || !keyFile || !password) { + res.status(400).json({ error: 'cerFile, keyFile y password son requeridos' }); + return; + } + + // Validate file sizes (typical .cer/.key files are under 10KB, base64 ~33% larger) + const MAX_FILE_SIZE = 50_000; // 50KB base64 ≈ ~37KB binary + if (cerFile.length > MAX_FILE_SIZE || keyFile.length > MAX_FILE_SIZE) { + res.status(400).json({ error: 'Los archivos FIEL son demasiado grandes (máx 50KB)' }); + return; + } + + if (password.length > 256) { + res.status(400).json({ error: 'Contraseña FIEL demasiado larga' }); + return; + } + + const result = await uploadFiel(tenantId, cerFile, keyFile, password); + + if (!result.success) { + res.status(400).json({ error: result.message }); + return; + } + + // Crear recordatorios de vencimiento en el calendario + if (result.status?.validUntil && req.tenantPool) { + crearRecordatoriosEfirma(req.tenantPool, req.user!.userId, result.status.validUntil) + .catch(err => console.error('[FIEL] Error creando recordatorios de vencimiento:', err)); + } + + res.json({ + message: result.message, + status: result.status, + }); + } catch (error: any) { + console.error('[FIEL Controller] Error en upload:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Obtiene el estado de la FIEL configurada + */ +export async function status(req: Request, res: Response): Promise { + try { + const tenantId = effectiveTenantId(req); + const fielStatus = await getFielStatus(tenantId); + res.json(fielStatus); + } catch (error: any) { + console.error('[FIEL Controller] Error en status:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Elimina las credenciales FIEL + */ +export async function remove(req: Request, res: Response): Promise { + try { + const tenantId = effectiveTenantId(req); + const deleted = await deleteFiel(tenantId); + + if (!deleted) { + res.status(404).json({ error: 'No hay FIEL configurada' }); + return; + } + + res.json({ message: 'FIEL eliminada correctamente' }); + } catch (error: any) { + console.error('[FIEL Controller] Error en remove:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} diff --git a/apps/api/src/controllers/impuestos.controller.ts b/apps/api/src/controllers/impuestos.controller.ts new file mode 100644 index 0000000..1619997 --- /dev/null +++ b/apps/api/src/controllers/impuestos.controller.ts @@ -0,0 +1,171 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as impuestosService from '../services/impuestos.service.js'; +import { prisma } from '../config/database.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +function parseConciliacion(req: Request): boolean { + return req.query.conciliacion === 'true' || req.query.conciliacion === '1'; +} + +function parseFlag(req: Request, key: string, defaultValue = true): boolean { + const v = req.query[key]; + if (v === undefined || v === null) return defaultValue; + return v === 'true' || v === '1'; +} + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +export async function getIvaMensual(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const año = parseInt(req.query.año as string) || new Date().getFullYear(); + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + const considerarActivos = parseFlag(req, 'considerarActivos', true); + const considerarNCs = parseFlag(req, 'considerarNCs', true); + const data = await impuestosService.getIvaMensual(req.tenantPool, año, effectiveTenantId(req), conciliacion, contribuyenteId, considerarActivos, considerarNCs); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getIsrMensual(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + const año = parseInt(req.query.año as string) || new Date().getFullYear(); + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + const regimenClave = (req.query.regimenClave as string) || null; + const considerarActivos = parseFlag(req, 'considerarActivos', true); + const considerarNCs = parseFlag(req, 'considerarNCs', true); + const data = await impuestosService.getIsrMensual(req.tenantPool, año, effectiveTenantId(req), conciliacion, contribuyenteId, regimenClave, considerarActivos, considerarNCs); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getResumenIva(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth() + 1; + const lastDay = new Date(y, m, 0).getDate(); + const fechaInicio = (req.query.fechaInicio as string) || `${y}-${String(m).padStart(2, '0')}-01`; + const fechaFin = (req.query.fechaFin as string) || `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + const considerarActivos = parseFlag(req, 'considerarActivos', true); + const considerarNCs = parseFlag(req, 'considerarNCs', true); + + const resumen = await impuestosService.getResumenIva(req.tenantPool, fechaInicio, fechaFin, effectiveTenantId(req), conciliacion, contribuyenteId, considerarActivos, considerarNCs); + res.json(resumen); + } catch (error) { + next(error); + } +} + +export async function getResumenIsr(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth() + 1; + const lastDay = new Date(y, m, 0).getDate(); + const fechaInicio = (req.query.fechaInicio as string) || `${y}-${String(m).padStart(2, '0')}-01`; + const fechaFin = (req.query.fechaFin as string) || `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + const considerarActivos = parseFlag(req, 'considerarActivos', true); + const considerarNCs = parseFlag(req, 'considerarNCs', true); + + const resumen = await impuestosService.getResumenIsr(req.tenantPool, fechaInicio, fechaFin, effectiveTenantId(req), conciliacion, contribuyenteId, considerarActivos, considerarNCs); + res.json(resumen); + } catch (error) { + next(error); + } +} + +export async function getResumenIsrDesglosado(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); + } + + // fechaFin define mes_final + año. Default: último día del mes corriente. + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth() + 1; + const lastDay = new Date(y, m, 0).getDate(); + const fechaFin = (req.query.fechaFin as string) || `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + const conciliacion = parseConciliacion(req); + const contribuyenteId = (req.query.contribuyenteId as string) || null; + const considerarActivos = parseFlag(req, 'considerarActivos', true); + const considerarNCs = parseFlag(req, 'considerarNCs', true); + + const desglose = await impuestosService.getResumenIsrDesglosado( + req.tenantPool, + fechaFin, + effectiveTenantId(req), + conciliacion, + contribuyenteId, + considerarActivos, + considerarNCs, + ); + res.json(desglose); + } catch (error) { + next(error); + } +} + +export async function getCoeficiente(req: Request, res: Response, next: NextFunction) { + try { + const anio = parseInt(req.query.anio as string) || new Date().getFullYear(); + const tenantId = effectiveTenantId(req); + const row = await prisma.coeficienteUtilidad.findUnique({ + where: { tenantId_anio: { tenantId, anio } }, + }); + res.json({ anio, coeficiente: row ? Number(row.coeficiente) : null }); + } catch (error) { + next(error); + } +} + +export async function setCoeficiente(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') { + return res.status(403).json({ message: 'Solo el dueño puede configurar el coeficiente' }); + } + + const { anio, coeficiente } = req.body; + if (!anio || coeficiente === undefined || coeficiente === null) { + return res.status(400).json({ message: 'anio y coeficiente son requeridos' }); + } + + const tenantId = effectiveTenantId(req); + const row = await prisma.coeficienteUtilidad.upsert({ + where: { tenantId_anio: { tenantId, anio } }, + update: { coeficiente }, + create: { tenantId, anio, coeficiente }, + }); + + res.json({ anio: row.anio, coeficiente: Number(row.coeficiente) }); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/metricas.controller.ts b/apps/api/src/controllers/metricas.controller.ts new file mode 100644 index 0000000..1190daa --- /dev/null +++ b/apps/api/src/controllers/metricas.controller.ts @@ -0,0 +1,25 @@ +import type { Request, Response, NextFunction } from 'express'; +import { getMetricasMensuales } from '../services/metricas.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +export async function getMensuales(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = req.query.contribuyenteId as string; + const anio = Number(req.query.anio); + + if (!contribuyenteId || !anio) { + return next(new AppError(400, 'contribuyenteId y anio son requeridos')); + } + + const regimenFiscal = req.query.regimen as string | undefined; + const metricas = await getMetricasMensuales(req.tenantPool!, contribuyenteId, anio, regimenFiscal); + + const currentYear = new Date().getFullYear(); + return res.json({ + data: metricas, + source: anio < currentYear ? 'cold' : 'hot', + anio, + contribuyenteId, + }); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/notification-preferences.controller.ts b/apps/api/src/controllers/notification-preferences.controller.ts new file mode 100644 index 0000000..cda54d4 --- /dev/null +++ b/apps/api/src/controllers/notification-preferences.controller.ts @@ -0,0 +1,33 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { AppError } from '../middlewares/error.middleware.js'; +import { + EMAIL_TYPES, + getEmailPreferencesPorContribuyente, + setContribuyenteEmailPreferences, +} from '../services/notification-preferences.service.js'; + +export async function listPreferences(req: Request, res: Response, next: NextFunction) { + try { + const data = await getEmailPreferencesPorContribuyente(req.tenantPool!); + res.json({ emailTypes: EMAIL_TYPES, data }); + } catch (error) { + next(error); + } +} + +const updateSchema = z.object({ + contribuyenteId: z.string().uuid(), + preferences: z.record(z.string(), z.boolean()), +}); + +export async function updatePreferences(req: Request, res: Response, next: NextFunction) { + try { + const { contribuyenteId, preferences } = updateSchema.parse(req.body); + const updated = await setContribuyenteEmailPreferences(req.tenantPool!, contribuyenteId, preferences); + res.json({ contribuyenteId, preferences: updated }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} diff --git a/apps/api/src/controllers/obligaciones.controller.ts b/apps/api/src/controllers/obligaciones.controller.ts new file mode 100644 index 0000000..8291649 --- /dev/null +++ b/apps/api/src/controllers/obligaciones.controller.ts @@ -0,0 +1,111 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as obligacionesService from '../services/obligaciones.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +export async function getCatalogo(req: Request, res: Response, next: NextFunction) { + try { + return res.json({ data: obligacionesService.getCatalogo() }); + } catch (err) { return next(err); } +} + +export async function getObligaciones(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + const rows = await obligacionesService.getObligaciones(req.tenantPool!, contribuyenteId); + return res.json({ data: rows }); + } catch (err) { return next(err); } +} + +export async function initRecomendaciones(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + const { rfc, regimenes, tieneNomina } = req.body; + if (!rfc) return next(new AppError(400, 'rfc requerido')); + const count = await obligacionesService.initRecomendaciones( + req.tenantPool!, contribuyenteId, rfc, regimenes || [], tieneNomina ?? false + ); + return res.json({ message: `${count} obligaciones recomendadas agregadas`, count }); + } catch (err) { return next(err); } +} + +export async function addObligacion(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + const schema = z.object({ + catalogoId: z.string().optional(), + nombre: z.string().min(2), + fundamento: z.string().optional(), + frecuencia: z.string().optional(), + fechaLimite: z.string().optional(), + categoria: z.string().optional(), + }); + const data = schema.parse(req.body); + const row = await obligacionesService.addObligacion(req.tenantPool!, contribuyenteId, data); + return res.status(201).json(row); + } catch (err: any) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} + +export async function removeObligacion(req: Request, res: Response, next: NextFunction) { + try { + const ok = await obligacionesService.removeObligacion(req.tenantPool!, String(req.params.obligacionId)); + if (!ok) return next(new AppError(404, 'Obligación no encontrada')); + return res.json({ message: 'Obligación desactivada' }); + } catch (err) { return next(err); } +} + +export async function restoreObligacion(req: Request, res: Response, next: NextFunction) { + try { + const ok = await obligacionesService.restoreObligacion(req.tenantPool!, String(req.params.obligacionId)); + if (!ok) return next(new AppError(404, 'Obligación no encontrada')); + return res.json({ message: 'Obligación restaurada' }); + } catch (err) { return next(err); } +} + +export async function completeObligacion(req: Request, res: Response, next: NextFunction) { + try { + const periodo = req.body.periodo || new Date().toISOString().substring(0, 7); + const ok = await obligacionesService.completeObligacion(req.tenantPool!, String(req.params.obligacionId), req.user!.userId, periodo); + if (!ok) return next(new AppError(404, 'Obligación no encontrada')); + return res.json({ message: 'Obligación marcada como completada' }); + } catch (err) { return next(err); } +} + +export async function uncompleteObligacion(req: Request, res: Response, next: NextFunction) { + try { + const ok = await obligacionesService.uncompleteObligacion(req.tenantPool!, String(req.params.obligacionId)); + if (!ok) return next(new AppError(404, 'Obligación no encontrada')); + return res.json({ message: 'Obligación desmarcada' }); + } catch (err) { return next(err); } +} + +export async function getObligacionesPorPeriodo(req: Request, res: Response, next: NextFunction) { + try { + const contribuyenteId = String(req.params.id); + const periodo = (req.query.periodo as string) || new Date().toISOString().substring(0, 7); + const incluirAtrasados = req.query.atrasados !== 'false'; + const rows = await obligacionesService.getObligacionesPorPeriodo(req.tenantPool!, contribuyenteId, periodo, incluirAtrasados); + return res.json({ data: rows, periodo }); + } catch (err) { return next(err); } +} + +export async function completePeriodo(req: Request, res: Response, next: NextFunction) { + try { + const { periodo, notas } = req.body; + if (!periodo) return next(new AppError(400, 'periodo requerido (YYYY-MM)')); + await obligacionesService.completePeriodo(req.tenantPool!, String(req.params.obligacionId), periodo, req.user!.userId, notas); + return res.json({ message: 'Obligación completada para el periodo' }); + } catch (err) { return next(err); } +} + +export async function uncompletePeriodo(req: Request, res: Response, next: NextFunction) { + try { + const periodo = req.body.periodo || req.query.periodo; + if (!periodo) return next(new AppError(400, 'periodo requerido')); + await obligacionesService.uncompletePeriodo(req.tenantPool!, String(req.params.obligacionId), periodo as string); + return res.json({ message: 'Completación removida' }); + } catch (err) { return next(err); } +} diff --git a/apps/api/src/controllers/papeleria.controller.ts b/apps/api/src/controllers/papeleria.controller.ts new file mode 100644 index 0000000..6812301 --- /dev/null +++ b/apps/api/src/controllers/papeleria.controller.ts @@ -0,0 +1,263 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { AppError } from '../middlewares/error.middleware.js'; +import * as papeleriaService from '../services/papeleria.service.js'; +import { emailService } from '../services/email/email.service.js'; +import { getTenantOwnerEmails, getUserEmailById } from '../utils/memberships.js'; +import { env } from '../config/env.js'; +import { prisma } from '../config/database.js'; + +function rejectClienteRole(req: Request): void { + if (req.user?.role === 'cliente') { + throw new AppError(403, 'Papelería no disponible para usuarios cliente'); + } +} + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +const uploadSchema = z.object({ + contribuyenteId: z.string().uuid(), + nombre: z.string().min(1).max(255), + descripcion: z.string().max(2000).nullable().optional(), + anio: z.number().int().min(2000).max(2100), + mes: z.number().int().min(1).max(12), + requiereAprobacion: z.boolean(), + archivoBase64: z.string().min(1), + archivoFilename: z.string().min(1).max(255), + archivoMime: z.string().min(1).max(100), +}); + +export async function upload(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const data = uploadSchema.parse(req.body); + const archivo = Buffer.from(data.archivoBase64, 'base64'); + + const item = await papeleriaService.uploadPapeleria(req.tenantPool!, { + contribuyenteId: data.contribuyenteId, + nombre: data.nombre, + descripcion: data.descripcion ?? null, + anio: data.anio, + mes: data.mes, + requiereAprobacion: data.requiereAprobacion, + archivo, + archivoFilename: data.archivoFilename, + archivoMime: data.archivoMime, + subidoPor: req.user!.userId, + }); + + // Notificación a aprobadores si la papelería requiere aprobación. + if (item.requiereAprobacion) { + notifyAprobacionRequerida(req, item).catch(err => + console.error('[papeleria.upload] notify aprobadores failed:', err?.message || err), + ); + } + + res.status(201).json(item); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + if (error?.message?.startsWith('Formato no permitido') || error?.message?.startsWith('Archivo excede')) { + return next(new AppError(400, error.message)); + } + next(error); + } +} + +const listSchema = z.object({ + contribuyenteId: z.string().uuid(), + anio: z.string().regex(/^\d{4}$/).optional(), + mes: z.string().regex(/^\d{1,2}$/).optional(), + estado: z.enum(['pendiente', 'aprobado', 'rechazado', 'sin_aprobacion']).optional(), +}); + +export async function list(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const q = listSchema.parse(req.query); + const items = await papeleriaService.listPapeleria(req.tenantPool!, { + contribuyenteId: q.contribuyenteId, + anio: q.anio ? parseInt(q.anio, 10) : undefined, + mes: q.mes ? parseInt(q.mes, 10) : undefined, + estado: q.estado, + }); + res.json(items); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function download(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const id = parseInt(String(req.params.id), 10); + if (isNaN(id)) return next(new AppError(400, 'ID inválido')); + const file = await papeleriaService.downloadArchivo(req.tenantPool!, id); + if (!file) return next(new AppError(404, 'Documento no encontrado')); + res.setHeader('Content-Type', file.mime); + res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(file.filename)}"`); + res.send(file.archivo); + } catch (error) { + next(error); + } +} + +export async function aprobar(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const id = parseInt(String(req.params.id), 10); + if (isNaN(id)) return next(new AppError(400, 'ID inválido')); + const item = await papeleriaService.aprobar( + req.tenantPool!, id, req.user!.userId, req.user!.role, + ); + if (!item) return next(new AppError(404, 'Documento no encontrado o no requiere aprobación')); + notifyDecisionAuxiliar(req, item).catch(err => + console.error('[papeleria.aprobar] notify auxiliar failed:', err?.message || err), + ); + res.json(item); + } catch (error: any) { + if (error?.message?.startsWith('Solo owner')) return next(new AppError(403, error.message)); + next(error); + } +} + +const rechazarSchema = z.object({ comentario: z.string().max(2000).nullable().optional() }); + +export async function rechazar(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const id = parseInt(String(req.params.id), 10); + if (isNaN(id)) return next(new AppError(400, 'ID inválido')); + const { comentario } = rechazarSchema.parse(req.body); + const item = await papeleriaService.rechazar( + req.tenantPool!, id, req.user!.userId, req.user!.role, comentario ?? null, + ); + if (!item) return next(new AppError(404, 'Documento no encontrado o no requiere aprobación')); + notifyDecisionAuxiliar(req, item).catch(err => + console.error('[papeleria.rechazar] notify auxiliar failed:', err?.message || err), + ); + res.json(item); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + if (error?.message?.startsWith('Solo owner')) return next(new AppError(403, error.message)); + next(error); + } +} + +export async function eliminar(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const id = parseInt(String(req.params.id), 10); + if (isNaN(id)) return next(new AppError(400, 'ID inválido')); + const ok = await papeleriaService.eliminar(req.tenantPool!, id); + if (!ok) return next(new AppError(404, 'Documento no encontrado')); + res.status(204).send(); + } catch (error) { + next(error); + } +} + +// ─── Notificaciones ─── + +/** + * Notifica a owners y supervisores cuando una papelería requiere aprobación. + * Owners se obtienen de tenant_memberships (BD central). Supervisores se + * resuelven leyendo carteras del tenant. + */ +async function notifyAprobacionRequerida( + req: Request, + item: papeleriaService.PapeleriaItem, +): Promise { + const tenantId = effectiveTenantId(req); + + // Owners del despacho + const recipients = new Set(await getTenantOwnerEmails(tenantId)); + + // Supervisores: cualquier user con rol 'supervisor' o 'cfo' que pertenezca a este tenant. + // Buscamos vía tenant_memberships + roles. + const supervisores = await prisma.tenantMembership.findMany({ + where: { tenantId, active: true, rol: { nombre: { in: ['supervisor', 'cfo'] } } }, + include: { user: { select: { email: true, active: true } } }, + }); + for (const m of supervisores) { + if (m.user.active && m.user.email) recipients.add(m.user.email); + } + + // No notificarse a sí mismo + recipients.delete(req.user!.email); + + if (recipients.size === 0) return; + + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { nombre: true }, + }); + + const { rows } = await req.tenantPool!.query<{ rfc: string; nombre: string }>( + `SELECT c.rfc, eg.nombre FROM contribuyentes c + JOIN entidades_gestionadas eg ON eg.id = c.entidad_id + WHERE c.entidad_id = $1`, + [item.contribuyenteId], + ); + if (rows.length === 0) return; + + const link = `${env.FRONTEND_URL}/documentos`; + const meses = ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic']; + const periodo = `${meses[item.mes - 1]} ${item.anio}`; + + for (const to of recipients) { + try { + await emailService.sendPapeleriaAprobacionRequerida(to, { + contribuyenteRfc: rows[0].rfc, + contribuyenteNombre: rows[0].nombre, + despachoNombre: tenant?.nombre, + nombreDocumento: item.nombre, + descripcion: item.descripcion, + periodo, + subidoPor: req.user!.email, + link, + }); + } catch (err: any) { + console.error(`[Email] papeleria-aprobacion a ${to}:`, err?.message || err); + } + } +} + +/** + * Notifica al uploader (auxiliar) cuando un documento que él subió fue + * aprobado o rechazado. Solo manda si quien aprobó/rechazó NO es el mismo + * uploader (caso edge: owner sube su propia papelería). + */ +async function notifyDecisionAuxiliar( + req: Request, + item: papeleriaService.PapeleriaItem, +): Promise { + if (item.subidoPor === req.user!.userId) return; + const auxiliarEmail = await getUserEmailById(item.subidoPor); + if (!auxiliarEmail) return; + + const { rows } = await req.tenantPool!.query<{ rfc: string; nombre: string }>( + `SELECT c.rfc, eg.nombre FROM contribuyentes c + JOIN entidades_gestionadas eg ON eg.id = c.entidad_id + WHERE c.entidad_id = $1`, + [item.contribuyenteId], + ); + if (rows.length === 0) return; + + const link = `${env.FRONTEND_URL}/documentos`; + const meses = ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic']; + const periodo = `${meses[item.mes - 1]} ${item.anio}`; + + await emailService.sendPapeleriaDecision(auxiliarEmail, { + contribuyenteRfc: rows[0].rfc, + contribuyenteNombre: rows[0].nombre, + nombreDocumento: item.nombre, + estado: item.estado as 'aprobado' | 'rechazado', + revisor: req.user!.email, + comentario: item.comentarioRechazo, + periodo, + link, + }); +} diff --git a/apps/api/src/controllers/plan-catalogo.controller.ts b/apps/api/src/controllers/plan-catalogo.controller.ts new file mode 100644 index 0000000..dbb4c59 --- /dev/null +++ b/apps/api/src/controllers/plan-catalogo.controller.ts @@ -0,0 +1,92 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as planService from '../services/plan-catalogo.service.js'; +import { prisma } from '../config/database.js'; +import { invalidateDespachoPlanCache } from '../services/plan-catalogo.service.js'; +import { canEditPrices } from '../utils/platform-admin.js'; +import { AppError } from '../middlewares/error.middleware.js'; + +export async function getPlans(req: Request, res: Response, next: NextFunction) { + try { + const vertical = req.query.vertical as string | undefined; + const plans = await planService.listPlans(vertical); + return res.json({ data: plans }); + } catch (err) { return next(err); } +} + +export async function getAddons(req: Request, res: Response, next: NextFunction) { + try { + const vertical = req.query.vertical as string | undefined; + const addons = await planService.listAddons(vertical); + return res.json({ data: addons }); + } catch (err) { return next(err); } +} + +export async function getPlan(req: Request, res: Response, next: NextFunction) { + try { + const plan = await planService.getPlanByCodename(String(req.params.codename)); + if (!plan) return res.status(404).json({ message: 'Plan no encontrado' }); + return res.json(plan); + } catch (err) { return next(err); } +} + +// ============================================================================ +// Catálogo despacho — limits + precios editables por admin global +// ============================================================================ + +/** GET /api/planes/despacho — devuelve los 6 planes con limits + precios. */ +export async function listDespachoCatalogo(_req: Request, res: Response, next: NextFunction) { + try { + const plans = await planService.getAllDespachoPlanLimits(); + return res.json({ data: plans }); + } catch (err) { return next(err); } +} + +const updateSchema = z.object({ + nombre: z.string().min(1).max(50).optional(), + monthly: z.number().nullable().optional(), + firstYear: z.number().nullable().optional(), + renewal: z.number().nullable().optional(), + permiteMonthly: z.boolean().optional(), + maxRfcs: z.number().int().optional(), + maxUsers: z.number().int().optional(), + timbresIncluidosMes: z.number().int().nonnegative().optional(), + dbMode: z.enum(['BYO', 'MANAGED']).optional(), + permiteServidorBackup: z.boolean().optional(), + permiteSatIncremental: z.boolean().optional(), +}); + +/** PATCH /api/planes/despacho/:plan — actualiza precios/limits. Solo admin con canEditPrices. */ +export async function updateDespachoCatalogo(req: Request, res: Response, next: NextFunction) { + try { + if (!req.user || !(await canEditPrices(req.user.userId))) { + throw new AppError(403, 'Solo admin global puede editar el catálogo'); + } + const plan = String(req.params.plan); + const data = updateSchema.parse(req.body); + + const existing = await prisma.despachoPlanPrice.findUnique({ where: { plan } }); + if (!existing) throw new AppError(404, `Plan '${plan}' no encontrado`); + + const updated = await prisma.despachoPlanPrice.update({ where: { plan }, data }); + invalidateDespachoPlanCache(); + + return res.json({ + plan: updated.plan, + nombre: updated.nombre, + monthly: updated.monthly !== null ? Number(updated.monthly) : null, + firstYear: updated.firstYear !== null ? Number(updated.firstYear) : null, + renewal: updated.renewal !== null ? Number(updated.renewal) : null, + permiteMonthly: updated.permiteMonthly, + maxRfcs: updated.maxRfcs, + maxUsers: updated.maxUsers, + timbresIncluidosMes: updated.timbresIncluidosMes, + dbMode: updated.dbMode, + permiteServidorBackup: updated.permiteServidorBackup, + permiteSatIncremental: updated.permiteSatIncremental, + }); + } catch (err) { + if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); + return next(err); + } +} diff --git a/apps/api/src/controllers/platform-staff.controller.ts b/apps/api/src/controllers/platform-staff.controller.ts new file mode 100644 index 0000000..b358e01 --- /dev/null +++ b/apps/api/src/controllers/platform-staff.controller.ts @@ -0,0 +1,187 @@ +import type { Request, Response, NextFunction } from 'express'; +import { prisma } from '../config/database.js'; +import { hasPlatformRole, invalidatePlatformRolesCache, type PlatformRole } from '../utils/platform-admin.js'; +import { auditFromReq } from '../utils/audit.js'; + +const VALID_ROLES: PlatformRole[] = ['platform_admin', 'platform_ti', 'platform_support', 'platform_sales', 'platform_finance']; +const SUPERSET_ROLES: PlatformRole[] = ['platform_admin', 'platform_ti']; + +async function requirePlatformAdmin(req: Request, res: Response): Promise { + const ok = await hasPlatformRole(req.user?.userId, 'platform_admin'); + if (!ok) { + res.status(403).json({ message: 'Solo platform_admin puede gestionar staff' }); + } + return ok; +} + +/** + * Lista users que tienen al menos un platform role + users candidatos a serlo. + * Admin global (platform_admin) only. + */ +export async function listStaff(req: Request, res: Response, next: NextFunction) { + try { + if (!(await requirePlatformAdmin(req, res))) return; + + // Todos los users con al menos un platform role + const roles = await prisma.userPlatformRole.findMany({ + include: { + user: { + select: { + id: true, email: true, nombre: true, active: true, + // Tenant principal del staff: el primer membership owner por joinedAt + // ASC. Se incluye solo para mostrar contexto en la UI admin. + memberships: { + where: { active: true, isOwner: true }, + orderBy: { joinedAt: 'asc' }, + take: 1, + include: { tenant: { select: { id: true, nombre: true, rfc: true } } }, + }, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + + // Agrupa por user + const byUser = new Map(); + for (const r of roles) { + const existing = byUser.get(r.userId); + if (existing) { + existing.roles.push(r.role); + } else { + const { memberships, ...userBase } = r.user; + byUser.set(r.userId, { + ...userBase, + tenant: memberships[0]?.tenant ?? null, + roles: [r.role], + }); + } + } + res.json(Array.from(byUser.values())); + } catch (error) { + next(error); + } +} + +/** + * Busca users por email (para agregar nuevos staff). Admin global only. + */ +export async function searchUsers(req: Request, res: Response, next: NextFunction) { + try { + if (!(await requirePlatformAdmin(req, res))) return; + + const q = String(req.query.q || '').trim(); + if (q.length < 2) return res.json([]); + + const users = await prisma.user.findMany({ + where: { + OR: [ + { email: { contains: q, mode: 'insensitive' } }, + { nombre: { contains: q, mode: 'insensitive' } }, + ], + }, + select: { + id: true, email: true, nombre: true, active: true, + memberships: { + where: { active: true, isOwner: true }, + orderBy: { joinedAt: 'asc' }, + take: 1, + include: { tenant: { select: { id: true, nombre: true, rfc: true } } }, + }, + }, + take: 10, + }); + res.json(users.map(u => { + const { memberships, ...rest } = u; + return { ...rest, tenant: memberships[0]?.tenant ?? null }; + })); + } catch (error) { + next(error); + } +} + +/** + * Asigna un rol a un user. Idempotente (si ya existe, no duplica). + */ +export async function grantRole(req: Request, res: Response, next: NextFunction) { + try { + if (!(await requirePlatformAdmin(req, res))) return; + + const { userId, role } = req.body; + if (!userId || typeof userId !== 'string') { + return res.status(400).json({ message: 'userId requerido' }); + } + if (!VALID_ROLES.includes(role)) { + return res.status(400).json({ message: `role inválido. Valores: ${VALID_ROLES.join(', ')}` }); + } + + const user = await prisma.user.findUnique({ where: { id: userId }, select: { id: true, email: true } }); + if (!user) return res.status(404).json({ message: 'Usuario no encontrado' }); + + await prisma.userPlatformRole.upsert({ + where: { userId_role: { userId, role } }, + create: { userId, role, createdBy: req.user!.userId }, + update: {}, + }); + + invalidatePlatformRolesCache(userId); + + auditFromReq(req, 'platform_role.granted', { + entityType: 'User', + entityId: userId, + metadata: { role, targetEmail: user.email }, + }); + + res.json({ ok: true }); + } catch (error) { + next(error); + } +} + +/** + * Quita un rol a un user. Protección: no puedes quitarte tu propio `platform_admin` + * si eres el último admin (evita bootstrap problem — nadie queda con acceso). + */ +export async function revokeRole(req: Request, res: Response, next: NextFunction) { + try { + if (!(await requirePlatformAdmin(req, res))) return; + + const { userId, role } = req.body; + if (!userId || typeof userId !== 'string') { + return res.status(400).json({ message: 'userId requerido' }); + } + if (!VALID_ROLES.includes(role)) { + return res.status(400).json({ message: 'role inválido' }); + } + + // Protección: no quitar tu último rol superset (admin o TI) — evita bootstrap problem + if (SUPERSET_ROLES.includes(role) && userId === req.user!.userId) { + const supersetCount = await prisma.userPlatformRole.count({ + where: { role: { in: SUPERSET_ROLES } }, + }); + if (supersetCount <= 1) { + return res.status(400).json({ + message: 'No puedes quitar tu propio rol superset — serías el último con acceso transversal. Asigna platform_admin o platform_ti a otro usuario primero.', + }); + } + } + + const user = await prisma.user.findUnique({ where: { id: userId }, select: { email: true } }); + + await prisma.userPlatformRole.deleteMany({ + where: { userId, role }, + }); + + invalidatePlatformRolesCache(userId); + + auditFromReq(req, 'platform_role.revoked', { + entityType: 'User', + entityId: userId, + metadata: { role, targetEmail: user?.email }, + }); + + res.json({ ok: true }); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/regimen.controller.ts b/apps/api/src/controllers/regimen.controller.ts new file mode 100644 index 0000000..cad5357 --- /dev/null +++ b/apps/api/src/controllers/regimen.controller.ts @@ -0,0 +1,70 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as regimenService from '../services/regimen.service.js'; + +/** Resuelve el tenantId efectivo (impersonación o propio) */ +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +export async function getAllRegimenes(req: Request, res: Response, next: NextFunction) { + try { + const regimenes = await regimenService.getAllRegimenes(); + res.json(regimenes); + } catch (error) { + next(error); + } +} + +export async function getActivos(req: Request, res: Response, next: NextFunction) { + try { + const activos = await regimenService.getRegimenesActivos(effectiveTenantId(req)); + res.json(activos); + } catch (error) { + next(error); + } +} + +export async function setActivos(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') { + return res.status(403).json({ message: 'Solo el dueño puede configurar regímenes' }); + } + + const { regimenIds } = req.body; + if (!Array.isArray(regimenIds)) { + return res.status(400).json({ message: 'regimenIds debe ser un array' }); + } + + const result = await regimenService.setRegimenesActivos(effectiveTenantId(req), regimenIds); + res.json(result); + } catch (error) { + next(error); + } +} + +export async function getIgnorados(req: Request, res: Response, next: NextFunction) { + try { + const ignorados = await regimenService.getRegimenesIgnorados(effectiveTenantId(req)); + res.json(ignorados); + } catch (error) { + next(error); + } +} + +export async function setIgnorados(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') { + return res.status(403).json({ message: 'Solo el dueño puede configurar regímenes' }); + } + + const { regimenIds } = req.body; + if (!Array.isArray(regimenIds)) { + return res.status(400).json({ message: 'regimenIds debe ser un array' }); + } + + const result = await regimenService.setRegimenesIgnorados(effectiveTenantId(req), regimenIds); + res.json(result); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/reportes.controller.ts b/apps/api/src/controllers/reportes.controller.ts new file mode 100644 index 0000000..6d44e22 --- /dev/null +++ b/apps/api/src/controllers/reportes.controller.ts @@ -0,0 +1,85 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as reportesService from '../services/reportes.service.js'; + +export async function getEstadoResultados(req: Request, res: Response, next: NextFunction) { + try { + const { fechaInicio, fechaFin, contribuyenteId } = req.query; + const now = new Date(); + const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; + const fin = (fechaFin as string) || now.toISOString().split('T')[0]; + + const data = await reportesService.getEstadoResultados(req.tenantPool!, inicio, fin, req.user!.tenantId, contribuyenteId as string | undefined || null); + res.json(data); + } catch (error) { + console.error('[reportes] Error en getEstadoResultados:', error); + next(error); + } +} + +export async function getFlujoEfectivo(req: Request, res: Response, next: NextFunction) { + try { + const { fechaInicio, fechaFin, contribuyenteId } = req.query; + const now = new Date(); + const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; + const fin = (fechaFin as string) || now.toISOString().split('T')[0]; + + const data = await reportesService.getFlujoEfectivo(req.tenantPool!, inicio, fin, contribuyenteId as string | undefined || null); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getComparativo(req: Request, res: Response, next: NextFunction) { + try { + const { contribuyenteId } = req.query; + const año = parseInt(req.query.año as string) || new Date().getFullYear(); + const data = await reportesService.getComparativo(req.tenantPool!, año, contribuyenteId as string | undefined || null); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getCuentasXPagar(req: Request, res: Response, next: NextFunction) { + try { + const { fechaInicio, fechaFin, regimen, contribuyenteId } = req.query; + const now = new Date(); + const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; + const fin = (fechaFin as string) || now.toISOString().split('T')[0]; + + const data = await reportesService.getCuentasXPagar(req.tenantPool!, inicio, fin, regimen as string, contribuyenteId as string | undefined || null); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getCuentasXCobrar(req: Request, res: Response, next: NextFunction) { + try { + const { fechaInicio, fechaFin, regimen, contribuyenteId } = req.query; + const now = new Date(); + const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; + const fin = (fechaFin as string) || now.toISOString().split('T')[0]; + + const data = await reportesService.getCuentasXCobrar(req.tenantPool!, inicio, fin, regimen as string, contribuyenteId as string | undefined || null); + res.json(data); + } catch (error) { + next(error); + } +} + +export async function getConcentradoRfc(req: Request, res: Response, next: NextFunction) { + try { + const { fechaInicio, fechaFin, tipo, contribuyenteId } = req.query; + const now = new Date(); + const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; + const fin = (fechaFin as string) || now.toISOString().split('T')[0]; + const tipoRfc = (tipo as 'cliente' | 'proveedor') || 'cliente'; + + const data = await reportesService.getConcentradoRfc(req.tenantPool!, inicio, fin, tipoRfc, contribuyenteId as string | undefined || null); + res.json(data); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/sat.controller.ts b/apps/api/src/controllers/sat.controller.ts new file mode 100644 index 0000000..fbb1aaa --- /dev/null +++ b/apps/api/src/controllers/sat.controller.ts @@ -0,0 +1,168 @@ +import type { Request, Response } from 'express'; +import { + startSync, + getSyncStatus, + getSyncHistory, + retryJob, +} from '../services/sat/sat.service.js'; +import { getJobInfo, runSatSyncJobManually } from '../jobs/sat-sync.job.js'; +import type { StartSyncRequest } from '@horux/shared'; +import { isGlobalAdmin } from '../utils/global-admin.js'; + +function effectiveTenantId(req: Request): string { + return req.viewingTenantId || req.user!.tenantId; +} + +/** + * Inicia una sincronización manual + */ +export async function start(req: Request, res: Response): Promise { + try { + const tenantId = effectiveTenantId(req); + const { type, dateFrom, dateTo } = req.body as StartSyncRequest; + const contribuyenteId = req.body.contribuyenteId as string | undefined; + + const jobId = await startSync( + tenantId, + type || 'daily', + dateFrom ? new Date(dateFrom) : undefined, + dateTo ? new Date(dateTo) : undefined, + contribuyenteId || undefined + ); + + res.json({ + jobId, + message: 'Sincronización iniciada', + }); + } catch (error: any) { + console.error('[SAT Controller] Error en start:', error); + + if (error.message.includes('FIEL') || error.message.includes('sincronización en curso')) { + res.status(400).json({ error: error.message }); + return; + } + + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Obtiene el estado actual de sincronización + */ +export async function status(req: Request, res: Response): Promise { + try { + const tenantId = effectiveTenantId(req); + const contribuyenteId = req.query.contribuyenteId as string | undefined; + const syncStatus = await getSyncStatus(tenantId, contribuyenteId || undefined); + res.json(syncStatus); + } catch (error: any) { + console.error('[SAT Controller] Error en status:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Obtiene el historial de sincronizaciones + */ +export async function history(req: Request, res: Response): Promise { + try { + const tenantId = effectiveTenantId(req); + const page = parseInt(req.query.page as string) || 1; + const limit = parseInt(req.query.limit as string) || 10; + const contribuyenteId = req.query.contribuyenteId as string | undefined; + + const result = await getSyncHistory(tenantId, page, limit, contribuyenteId || undefined); + res.json({ + ...result, + page, + limit, + }); + } catch (error: any) { + console.error('[SAT Controller] Error en history:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Obtiene detalle de un job específico + */ +export async function jobDetail(req: Request, res: Response): Promise { + try { + const tenantId = effectiveTenantId(req); + const { id } = req.params; + const { jobs } = await getSyncHistory(tenantId, 1, 100); + const job = jobs.find(j => j.id === id); + + if (!job) { + res.status(404).json({ error: 'Job no encontrado' }); + return; + } + + res.json(job); + } catch (error: any) { + console.error('[SAT Controller] Error en jobDetail:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Reintenta un job fallido + */ +export async function retry(req: Request, res: Response): Promise { + try { + const id = req.params.id as string; + const newJobId = await retryJob(id); + + res.json({ + jobId: newJobId, + message: 'Job reintentado', + }); + } catch (error: any) { + console.error('[SAT Controller] Error en retry:', error); + + if (error.message.includes('no encontrado') || error.message.includes('Solo se pueden')) { + res.status(400).json({ error: error.message }); + return; + } + + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Obtiene información del job programado (solo admin global) + */ +export async function cronInfo(req: Request, res: Response): Promise { + try { + if (!(await isGlobalAdmin(req.user!.tenantId, req.user!.role))) { + res.status(403).json({ error: 'Solo el administrador global puede ver info del cron' }); + return; + } + const info = getJobInfo(); + res.json(info); + } catch (error: any) { + console.error('[SAT Controller] Error en cronInfo:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +/** + * Ejecuta el job de sincronización manualmente (solo admin global) + */ +export async function runCron(req: Request, res: Response): Promise { + try { + if (!(await isGlobalAdmin(req.user!.tenantId, req.user!.role))) { + res.status(403).json({ error: 'Solo el administrador global puede ejecutar el cron' }); + return; + } + // Ejecutar en background + runSatSyncJobManually().catch(err => + console.error('[SAT Controller] Error ejecutando cron manual:', err) + ); + + res.json({ message: 'Job de sincronización iniciado' }); + } catch (error: any) { + console.error('[SAT Controller] Error en runCron:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} diff --git a/apps/api/src/controllers/subscription.controller.ts b/apps/api/src/controllers/subscription.controller.ts new file mode 100644 index 0000000..39d6d3a --- /dev/null +++ b/apps/api/src/controllers/subscription.controller.ts @@ -0,0 +1,359 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as subscriptionService from '../services/payment/subscription.service.js'; +import { listActiveAddons, subscribeAddon, cancelAddon } from '../services/payment/addon.service.js'; +import { isGlobalAdmin } from '../utils/global-admin.js'; +import { auditFromReq } from '../utils/audit.js'; + +async function requireGlobalAdmin(req: Request, res: Response): Promise { + const isAdmin = await isGlobalAdmin(req.user!.tenantId, req.user!.role); + if (!isAdmin) { + res.status(403).json({ message: 'Solo el administrador global puede gestionar suscripciones' }); + } + return isAdmin; +} + +/** + * Permite si el usuario es admin global O si está consultando su propio tenant. + * Úsalo para endpoints de lectura/acción sobre la suscripción del mismo tenant + * del usuario (ver estado, generar link de pago pendiente). + */ +async function requireOwnTenantOrGlobalAdmin(req: Request, res: Response, targetTenantId: string): Promise { + if (targetTenantId === req.user!.tenantId) return true; + const isAdmin = await isGlobalAdmin(req.user!.tenantId, req.user!.role); + if (!isAdmin) { + res.status(403).json({ message: 'Solo puedes gestionar la suscripción de tu propio tenant' }); + } + return isAdmin; +} + +// (getPlans + updatePlanPrice eliminados — modelo PlanPrice legacy dropeado +// en migración 20260501160000_drop_plan_prices_legacy. Catálogo despacho +// vive en `despacho_plan_prices` editado vía /api/planes/despacho.) + +export async function getAllSubscriptions(req: Request, res: Response, next: NextFunction) { + try { + if (!(await requireGlobalAdmin(req, res))) return; + + const { prisma } = await import('../config/database.js'); + const subscriptions = await prisma.subscription.findMany({ + include: { + tenant: { + select: { id: true, nombre: true, rfc: true, plan: true, active: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + + res.json(subscriptions); + } catch (error) { + next(error); + } +} + +export async function getSubscription(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = String(req.params.tenantId); + if (!(await requireOwnTenantOrGlobalAdmin(req, res, tenantId))) return; + + const subscription = await subscriptionService.getActiveSubscription(tenantId); + if (!subscription) { + return res.status(404).json({ message: 'No se encontró suscripción' }); + } + res.json(subscription); + } catch (error) { + next(error); + } +} + +export async function generatePaymentLink(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = String(req.params.tenantId); + if (!(await requireOwnTenantOrGlobalAdmin(req, res, tenantId))) return; + + const result = await subscriptionService.generatePaymentLink(tenantId); + res.json(result); + } catch (error) { + next(error); + } +} + +export async function markAsPaid(req: Request, res: Response, next: NextFunction) { + try { + if (!(await requireGlobalAdmin(req, res))) return; + + const tenantId = String(req.params.tenantId); + const { amount } = req.body; + + if (!amount || amount <= 0) { + return res.status(400).json({ message: 'Monto inválido' }); + } + + const payment = await subscriptionService.markAsPaidManually(tenantId, amount); + res.json(payment); + } catch (error) { + next(error); + } +} + +export async function getPayments(req: Request, res: Response, next: NextFunction) { + try { + const tenantId = String(req.params.tenantId); + if (!(await requireOwnTenantOrGlobalAdmin(req, res, tenantId))) return; + + const payments = await subscriptionService.getPaymentHistory(tenantId); + res.json(payments); + } catch (error) { + next(error); + } +} + +// ============================================================================ +// Self-serve endpoints (actúan sobre el tenant del usuario autenticado) +// ============================================================================ + +type FrequencyInput = 'monthly' | 'annual'; +const VALID_PLANS = [ + 'business_control', 'business_cloud', + 'mi_empresa', 'mi_empresa_plus', +] as const; +// Planes despacho que se cobran SOLO anual. Mi Empresa y Mi Empresa+ aceptan +// monthly o annual (annual con descuento ~17% — paga 10 meses); Business +// Control y Enterprise siguen exclusivamente anuales. +const DESPACHO_ONLY_ANNUAL = new Set([ + 'business_control', 'business_cloud', +]); + +function validatePlanFrequency(body: any): { plan: typeof VALID_PLANS[number]; frequency: FrequencyInput } | { error: string } { + const plan = body?.plan; + const frequency = body?.frequency; + if (!plan || !VALID_PLANS.includes(plan)) { + return { error: `plan inválido. Valores aceptados: ${VALID_PLANS.join(', ')}` }; + } + if (frequency !== 'monthly' && frequency !== 'annual') { + return { error: `frequency inválida. Debe ser 'monthly' o 'annual'` }; + } + if (DESPACHO_ONLY_ANNUAL.has(plan) && frequency !== 'annual') { + return { error: `El plan ${plan} solo está disponible con frecuencia anual` }; + } + return { plan, frequency }; +} + +export async function startMyTrial(req: Request, res: Response, next: NextFunction) { + try { + const parsed = validatePlanFrequency(req.body); + if ('error' in parsed) return res.status(400).json({ message: parsed.error }); + + const result = await subscriptionService.startTrial({ + tenantId: req.user!.tenantId, + plan: parsed.plan, + frequency: parsed.frequency, + ownerUserId: req.user!.userId, + }); + res.status(201).json(result); + } catch (error: any) { + if ( + error.message?.includes('ya usó') || + error.message?.includes('Ya existe') || + error.message?.includes('no se puede') || + error.message?.includes('Ya consumiste') || + error.message?.includes('ya consumió') + ) { + return res.status(400).json({ message: error.message }); + } + next(error); + } +} + +export async function subscribeMe(req: Request, res: Response, next: NextFunction) { + try { + const parsed = validatePlanFrequency(req.body); + if ('error' in parsed) return res.status(400).json({ message: parsed.error }); + + const result = await subscriptionService.subscribe({ + tenantId: req.user!.tenantId, + plan: parsed.plan, + frequency: parsed.frequency, + payerEmail: req.user!.email, + }); + res.status(201).json(result); + } catch (error: any) { + const msg: string = error?.message || ''; + if (msg.includes('Ya existe') || msg.includes('custom')) { + return res.status(400).json({ message: msg }); + } + if (msg.includes('MercadoPago no está configurado')) { + return res.status(503).json({ message: msg }); + } + // Otros errores de MP al crear preapproval (monto inválido, email inválido, etc.) + if (msg.includes('Unauthorized access') || error?.status === 401) { + return res.status(503).json({ + message: 'MercadoPago rechazó la solicitud. Verifica que MP_ACCESS_TOKEN sea válido y esté vigente.', + }); + } + next(error); + } +} + +export async function changeMyPlan(req: Request, res: Response, next: NextFunction) { + try { + const parsed = validatePlanFrequency(req.body); + if ('error' in parsed) return res.status(400).json({ message: parsed.error }); + + const result = await subscriptionService.scheduleChange({ + tenantId: req.user!.tenantId, + newPlan: parsed.plan, + newFrequency: parsed.frequency, + }); + res.json(result); + } catch (error: any) { + if (error.message?.includes('iguales') || error.message?.includes('No hay') || error.message?.includes('custom')) { + return res.status(400).json({ message: error.message }); + } + next(error); + } +} + +export async function cancelMySubscription(req: Request, res: Response, next: NextFunction) { + try { + const result = await subscriptionService.cancelSubscription(req.user!.tenantId); + res.json(result); + } catch (error: any) { + if (error.message?.includes('No hay')) { + return res.status(400).json({ message: error.message }); + } + next(error); + } +} + +/** + * Reactiva suscripción cancelada que aún está dentro de su período pagado. + * Crea un preapproval nuevo en MP con start_date al final del período actual. + * Retorna paymentUrl para que el usuario autorice. + */ +export async function reactivateMe(req: Request, res: Response, next: NextFunction) { + try { + const result = await subscriptionService.reactivateSubscription({ + tenantId: req.user!.tenantId, + payerEmail: req.user!.email, + }); + res.status(201).json(result); + } catch (error: any) { + const msg: string = error?.message || ''; + if (msg.includes('No hay') || msg.includes('vencido') || msg.includes('custom')) { + return res.status(400).json({ message: msg }); + } + if (msg.includes('MercadoPago no está configurado')) { + return res.status(503).json({ message: msg }); + } + if (msg.includes('Unauthorized access') || error?.status === 401) { + return res.status(503).json({ + message: 'MercadoPago rechazó la solicitud. Verifica que MP_ACCESS_TOKEN sea válido.', + }); + } + next(error); + } +} + +/** + * Inicia un upgrade con cobro prorateado inmediato. Body: `{ plan }`. + * La frecuencia actual se preserva — para cambiar frecuencia usa `/me/change`. + * Retorna `{ checkoutUrl, proratedAmount }` — el cliente debe abrir la URL para que + * el usuario pague en MP. Al confirmarse el pago (webhook), se aplica el plan nuevo. + */ +export async function upgradeMe(req: Request, res: Response, next: NextFunction) { + try { + const plan = req.body?.plan; + if (!plan || !VALID_PLANS.includes(plan)) { + return res.status(400).json({ message: `plan inválido. Valores: ${VALID_PLANS.join(', ')}` }); + } + + const result = await subscriptionService.initiateUpgrade({ + tenantId: req.user!.tenantId, + newPlan: plan, + payerEmail: req.user!.email, + }); + res.status(201).json(result); + } catch (error: any) { + const msg: string = error?.message || ''; + if ( + msg.includes('No hay suscripción') || + msg.includes('en curso') || + msg.includes('no es un upgrade') || + msg.includes('días restantes') || + msg.includes('custom') || + msg.includes('Precio no configurado') + ) { + return res.status(400).json({ message: msg }); + } + if (msg.includes('MercadoPago no está configurado')) { + return res.status(503).json({ message: msg }); + } + if (msg.includes('Unauthorized access') || error?.status === 401) { + return res.status(503).json({ + message: 'MercadoPago rechazó la solicitud. Verifica que MP_ACCESS_TOKEN sea válido.', + }); + } + next(error); + } +} + +export async function cancelMyPendingUpgrade(req: Request, res: Response, next: NextFunction) { + try { + await subscriptionService.cancelPendingUpgrade(req.user!.tenantId); + res.json({ ok: true }); + } catch (error: any) { + if (error.message?.includes('No hay upgrade')) { + return res.status(400).json({ message: error.message }); + } + next(error); + } +} + +// ============================================================================ +// Addon endpoints (self-serve) +// ============================================================================ + +export async function getMyAddons(req: Request, res: Response, next: NextFunction) { + try { + // Query param `contribuyenteId` opcional: filtra al contribuyente específico. + // Sin param → retorna todos los add-ons del tenant (incluye los de todos los RFCs). + const contribuyenteId = typeof req.query.contribuyenteId === 'string' && req.query.contribuyenteId + ? req.query.contribuyenteId + : undefined; + const result = await listActiveAddons(req.user!.tenantId, contribuyenteId); + return res.json(result); + } catch (err) { return next(err); } +} + +export async function addMyAddon(req: Request, res: Response, next: NextFunction) { + try { + const { addonCodename, quantity, contribuyenteId } = req.body; + if (!addonCodename) return res.status(400).json({ message: 'addonCodename requerido' }); + + const result = await subscribeAddon({ + tenantId: req.user!.tenantId, + addonCodename, + quantity: quantity || 1, + payerEmail: req.user!.email, + contribuyenteId: typeof contribuyenteId === 'string' ? contribuyenteId : null, + }); + return res.status(201).json(result); + } catch (err: any) { + if (err.message?.includes('no disponible') || err.message?.includes('Ya tienes')) { + return res.status(409).json({ message: err.message }); + } + return next(err); + } +} + +export async function cancelMyAddon(req: Request, res: Response, next: NextFunction) { + try { + await cancelAddon(req.user!.tenantId, String(req.params.addonId)); + return res.json({ message: 'Addon cancelado' }); + } catch (err: any) { + if (err.message?.includes('no encontrado')) { + return res.status(404).json({ message: err.message }); + } + return next(err); + } +} diff --git a/apps/api/src/controllers/tareas.controller.ts b/apps/api/src/controllers/tareas.controller.ts new file mode 100644 index 0000000..c37803b --- /dev/null +++ b/apps/api/src/controllers/tareas.controller.ts @@ -0,0 +1,177 @@ +import type { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { AppError } from '../middlewares/error.middleware.js'; +import * as tareasService from '../services/tareas.service.js'; +import { emailService } from '../services/email/email.service.js'; +import { getUserEmailById } from '../utils/memberships.js'; +import { env } from '../config/env.js'; +import { prisma } from '../config/database.js'; + +/** + * Bloquea a usuarios rol `cliente` de cualquier endpoint de tareas. + * El cliente no debe ver tareas operativas internas del despacho. + */ +function rejectClienteRole(req: Request): void { + if (req.user?.role === 'cliente') { + throw new AppError(403, 'Tareas no disponibles para usuarios cliente'); + } +} + +const RECURRENCIAS = ['semanal', 'quincenal', 'mensual', 'bimestral', 'trimestral', 'semestral', 'anual'] as const; + +const tareaSchema = z.object({ + nombre: z.string().min(1).max(200), + descripcion: z.string().max(1000).nullable().optional(), + recurrencia: z.enum(RECURRENCIAS), + diaSemana: z.number().int().min(1).max(7).nullable().optional(), + diaMes: z.number().int().min(1).max(31).nullable().optional(), + soloSupervisorCompleta: z.boolean().optional(), + orden: z.number().int().optional(), +}); + +export async function listTareas(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const contribuyenteId = req.query.contribuyenteId as string | undefined; + if (!contribuyenteId) return next(new AppError(400, 'contribuyenteId requerido')); + const tareas = await tareasService.listTareasConPeriodoActual(req.tenantPool!, contribuyenteId); + res.json(tareas); + } catch (error) { + next(error); + } +} + +export async function createTarea(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const contribuyenteId = req.query.contribuyenteId as string | undefined; + if (!contribuyenteId) return next(new AppError(400, 'contribuyenteId requerido')); + const data = tareaSchema.parse(req.body); + const tarea = await tareasService.createTarea(req.tenantPool!, contribuyenteId, data); + res.status(201).json(tarea); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function updateTarea(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const data = tareaSchema.partial().parse(req.body); + const updated = await tareasService.updateTarea(req.tenantPool!, String(req.params.id), data); + if (!updated) return next(new AppError(404, 'Tarea no encontrada')); + res.json(updated); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function deleteTarea(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const ok = await tareasService.deleteTarea(req.tenantPool!, String(req.params.id)); + if (!ok) return next(new AppError(404, 'Tarea no encontrada')); + res.status(204).send(); + } catch (error) { + next(error); + } +} + +export async function completarPeriodo(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const { notas } = z.object({ notas: z.string().max(1000).nullable().optional() }).parse(req.body); + const result = await tareasService.completarPeriodo( + req.tenantPool!, + String(req.params.id), + req.user!.userId, + req.user!.role, + notas ?? null, + ); + if (!result) return next(new AppError(404, 'Periodo no encontrado')); + + // Notificar al auxiliar de la cartera SOLO cuando una tarea con + // solo_supervisor_completa=true fue marcada como completada por + // un supervisor/owner. Fire-and-forget — no bloquea la respuesta. + if (result.tarea.soloSupervisorCompleta) { + notifyAuxiliarTareaCompletada(req, result).catch(err => + console.error('[tareas.completar] notify auxiliar failed:', err?.message || err), + ); + } + res.json(result); + } catch (error: any) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + if (error?.message?.startsWith('Solo supervisor')) return next(new AppError(403, error.message)); + next(error); + } +} + +async function notifyAuxiliarTareaCompletada( + req: Request, + result: { periodo: tareasService.TareaPeriodo; tarea: tareasService.TareaCatalogo }, +): Promise { + const auxiliarUserId = await tareasService.getAuxiliarUserIdDeContribuyente( + req.tenantPool!, + result.tarea.contribuyenteId, + ); + if (!auxiliarUserId) return; + if (auxiliarUserId === req.user!.userId) return; // no notificarse a sí mismo + const auxiliarEmail = await getUserEmailById(auxiliarUserId); + if (!auxiliarEmail) return; + + // Datos del contribuyente y supervisor para el email + const { rows } = await req.tenantPool!.query<{ rfc: string; nombre: string }>( + `SELECT c.rfc, eg.nombre + FROM contribuyentes c + JOIN entidades_gestionadas eg ON eg.id = c.entidad_id + WHERE c.entidad_id = $1`, + [result.tarea.contribuyenteId], + ); + if (rows.length === 0) return; + + const auxiliarNombre = (await prisma.user.findUnique({ + where: { id: auxiliarUserId }, + select: { nombre: true }, + }))?.nombre || 'Auxiliar'; + + const fechaLimite = result.periodo.fechaLimite instanceof Date + ? result.periodo.fechaLimite.toLocaleDateString('es-MX', { dateStyle: 'long' }) + : new Date(String(result.periodo.fechaLimite)).toLocaleDateString('es-MX', { dateStyle: 'long' }); + + await emailService.sendTareaCompletada(auxiliarEmail, { + destinatarioNombre: auxiliarNombre, + contribuyenteNombre: rows[0].nombre, + contribuyenteRfc: rows[0].rfc, + tareaNombre: result.tarea.nombre, + tareaDescripcion: result.tarea.descripcion, + completadaPor: req.user!.email, + notas: result.periodo.notas, + fechaLimite, + link: `${env.FRONTEND_URL}/configuracion/obligaciones`, + }); +} + +export async function descompletarPeriodo(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const ok = await tareasService.descompletarPeriodo(req.tenantPool!, String(req.params.id)); + if (!ok) return next(new AppError(404, 'Periodo no encontrado')); + res.status(204).send(); + } catch (error) { + next(error); + } +} + +export async function seedDefaults(req: Request, res: Response, next: NextFunction) { + try { + rejectClienteRole(req); + const contribuyenteId = req.query.contribuyenteId as string | undefined; + if (!contribuyenteId) return next(new AppError(400, 'contribuyenteId requerido')); + const created = await tareasService.seedTareasDefault(req.tenantPool!, contribuyenteId); + res.json({ created }); + } catch (error) { + next(error); + } +} diff --git a/apps/api/src/controllers/tenants.controller.ts b/apps/api/src/controllers/tenants.controller.ts new file mode 100644 index 0000000..cf85aed --- /dev/null +++ b/apps/api/src/controllers/tenants.controller.ts @@ -0,0 +1,145 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as tenantsService from '../services/tenants.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { isGlobalAdmin } from '../utils/global-admin.js'; +import { isOwnerSomewhere } from '../utils/memberships.js'; + +async function requireGlobalAdmin(req: Request): Promise { + if (!(await isGlobalAdmin(req.user!.tenantId, req.user!.role))) { + throw new AppError(403, 'Solo el administrador global puede gestionar clientes'); + } +} + +export async function getAllTenants(req: Request, res: Response, next: NextFunction) { + try { + await requireGlobalAdmin(req); + + const tenants = await tenantsService.getAllTenants(); + res.json(tenants); + } catch (error) { + next(error); + } +} + +export async function getTenant(req: Request, res: Response, next: NextFunction) { + try { + await requireGlobalAdmin(req); + + const tenant = await tenantsService.getTenantById(String(req.params.id)); + if (!tenant) { + throw new AppError(404, 'Cliente no encontrado'); + } + + res.json(tenant); + } catch (error) { + next(error); + } +} + +export async function createTenant(req: Request, res: Response, next: NextFunction) { + try { + await requireGlobalAdmin(req); + + const { nombre, rfc, plan, adminEmail, adminNombre, amount, firstPaymentDueAt } = req.body; + + if (!nombre || !rfc || !adminEmail || !adminNombre) { + throw new AppError(400, 'Nombre, RFC, adminEmail y adminNombre son requeridos'); + } + + const result = await tenantsService.createTenant({ + nombre, + rfc, + plan, + adminEmail, + adminNombre, + amount: amount || 0, + firstPaymentDueAt: firstPaymentDueAt || null, + }); + + res.status(201).json(result); + } catch (error) { + next(error); + } +} + +export async function updateTenant(req: Request, res: Response, next: NextFunction) { + try { + await requireGlobalAdmin(req); + + const id = String(req.params.id); + const { nombre, rfc, plan, active } = req.body; + + const tenant = await tenantsService.updateTenant(id, { + nombre, + rfc, + plan, + active, + }); + + res.json(tenant); + } catch (error) { + next(error); + } +} + +export async function deleteTenant(req: Request, res: Response, next: NextFunction) { + try { + await requireGlobalAdmin(req); + + await tenantsService.deleteTenant(String(req.params.id)); + res.status(204).send(); + } catch (error) { + next(error); + } +} + +// ============================================================================ +// Self-serve (multi-tenant memberships) +// ============================================================================ + +/** + * Lista detallada de empresas del caller con estado de suscripción. Usado por + * `/mis-empresas`. A diferencia de `/auth/me`, incluye datos de subscription + * (status, currentPeriodEnd, pendingPlan, etc.). + */ +export async function getMyTenants(req: Request, res: Response, next: NextFunction) { + try { + const data = await tenantsService.getMyTenantsDetailed(req.user!.userId); + res.json(data); + } catch (error) { next(error); } +} + +const addTenantSchema = z.object({ + nombre: z.string().min(2, 'Nombre de empresa requerido'), + rfc: z.string().min(12).max(13, 'RFC inválido'), + plan: z.enum(['mi_empresa', 'mi_empresa_plus', 'business_control', 'business_cloud']).optional(), +}); + +/** + * Agrega una empresa (tenant nuevo) bajo el user autenticado. El caller se + * vuelve owner automáticamente vía TenantMembership. + */ +export async function addMyTenant(req: Request, res: Response, next: NextFunction) { + try { + const data = addTenantSchema.parse(req.body); + // Gate: solo users que son owner en al menos un tenant pueden agregar + // un RFC adicional. Un contador invitado a una empresa ajena no puede. + if (!(await isOwnerSomewhere(req.user!.userId))) { + throw new AppError(403, 'Solo los dueños pueden registrar empresas adicionales.'); + } + const result = await tenantsService.addTenantToOwner({ + userId: req.user!.userId, + nombre: data.nombre, + rfc: data.rfc, + plan: data.plan, + }); + res.status(201).json({ tenant: result.tenant }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + if (error instanceof Error && error.message.includes('RFC')) { + return next(new AppError(400, error.message)); + } + next(error); + } +} diff --git a/apps/api/src/controllers/usuarios.controller.ts b/apps/api/src/controllers/usuarios.controller.ts new file mode 100644 index 0000000..5bd9eff --- /dev/null +++ b/apps/api/src/controllers/usuarios.controller.ts @@ -0,0 +1,273 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as usuariosService from '../services/usuarios.service.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { isGlobalAdmin as checkGlobalAdmin } from '../utils/global-admin.js'; + +const inviteSchema = z.object({ + email: z.string().email('email inválido'), + nombre: z.string().min(2).max(100), + // Legacy Horux360 roles + Despacho-specific roles + role: z.enum(['contador', 'visor', 'auxiliar', 'supervisor', 'cliente']), + supervisorUserId: z.string().uuid().optional(), // Required when role=auxiliar +}); + +const updateSchema = z.object({ + nombre: z.string().min(2).max(100).optional(), + // Legacy Horux360 roles + Despacho-specific roles + role: z.enum(['contador', 'visor', 'auxiliar', 'supervisor', 'cliente']).optional(), + active: z.boolean().optional(), +}); + +const updateGlobalSchema = z.object({ + nombre: z.string().min(2).max(100).optional(), + role: z.enum(['owner', 'cfo', 'contador', 'visor', 'auxiliar', 'supervisor', 'cliente']).optional(), + active: z.boolean().optional(), + tenantId: z.string().uuid().optional(), +}); + +async function isGlobalAdmin(req: Request): Promise { + return checkGlobalAdmin(req.user!.tenantId, req.user!.role); +} + +export async function getUsuarios(req: Request, res: Response, next: NextFunction) { + try { + const usuarios = await usuariosService.getUsuarios(req.user!.tenantId); + res.json(usuarios); + } catch (error) { + next(error); + } +} + +/** + * Obtiene todos los usuarios de todas las empresas (solo admin global) + */ +export async function getAllUsuarios(req: Request, res: Response, next: NextFunction) { + try { + if (!(await isGlobalAdmin(req))) { + throw new AppError(403, 'Solo el administrador global puede ver todos los usuarios'); + } + const usuarios = await usuariosService.getAllUsuarios(); + res.json(usuarios); + } catch (error) { + next(error); + } +} + +export async function inviteUsuario(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') { + throw new AppError(403, 'Solo los dueños pueden invitar usuarios'); + } + const data = inviteSchema.parse(req.body); + + // Validate: auxiliar requires a supervisor + if (data.role === 'auxiliar' && !data.supervisorUserId) { + throw new AppError(400, 'Debes asignar un supervisor al auxiliar'); + } + + const usuario = await usuariosService.inviteUsuario(req.user!.tenantId, data); + + // Store auxiliar→supervisor relationship in tenant DB + if (data.role === 'auxiliar' && data.supervisorUserId && req.tenantPool) { + await req.tenantPool.query( + `INSERT INTO auxiliar_supervisores (auxiliar_user_id, supervisor_user_id) + VALUES ($1, $2) ON CONFLICT (auxiliar_user_id) DO UPDATE SET supervisor_user_id = $2`, + [usuario.id, data.supervisorUserId], + ); + } + + res.status(201).json(usuario); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function updateUsuario(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') { + throw new AppError(403, 'Solo los dueños pueden modificar usuarios'); + } + const userId = req.params.id as string; + const data = updateSchema.parse(req.body); + const usuario = await usuariosService.updateUsuario(req.user!.tenantId, userId, data); + res.json(usuario); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +/** + * Lee el supervisor actualmente asignado a un auxiliar. Resuelve desde 3 + * fuentes (en orden de prioridad): + * 1. `auxiliar_supervisores` (override explícito del owner desde /usuarios). + * 2. Cartera donde el user es `auxiliar_user_id` y la misma tiene supervisor. + * 3. Subcartera donde el user es `auxiliar_user_id`; el supervisor viene + * del cartera padre. + * + * Devuelve `null` si no aparece en ninguna. + */ +export async function getSupervisor(req: Request, res: Response, next: NextFunction) { + try { + if (!req.tenantPool) throw new AppError(500, 'Tenant pool no disponible'); + const userId = String(req.params.id); + const { rows } = await req.tenantPool.query<{ supervisor_user_id: string }>( + `SELECT supervisor_user_id FROM ( + SELECT supervisor_user_id, 1 AS prio FROM auxiliar_supervisores + WHERE auxiliar_user_id = $1 + UNION ALL + SELECT supervisor_user_id, 2 AS prio FROM carteras + WHERE auxiliar_user_id = $1 AND supervisor_user_id IS NOT NULL + UNION ALL + SELECT p.supervisor_user_id, 3 AS prio + FROM carteras sub + JOIN carteras p ON p.id = sub.parent_id + WHERE sub.auxiliar_user_id = $1 AND p.supervisor_user_id IS NOT NULL + ) t + WHERE supervisor_user_id IS NOT NULL + ORDER BY prio + LIMIT 1`, + [userId], + ); + res.json({ supervisorUserId: rows[0]?.supervisor_user_id ?? null }); + } catch (error) { + next(error); + } +} + +const supervisorSchema = z.object({ + supervisorUserId: z.string().uuid().nullable(), +}); + +/** + * Asigna o elimina el supervisor de un auxiliar (BD tenant). + * Solo owner/cfo. Pasar `null` borra la asignación. + */ +export async function updateSupervisor(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner' && req.user!.role !== 'cfo') { + throw new AppError(403, 'Solo el owner puede asignar supervisores'); + } + if (!req.tenantPool) throw new AppError(500, 'Tenant pool no disponible'); + const userId = String(req.params.id); + const { supervisorUserId } = supervisorSchema.parse(req.body); + + if (supervisorUserId === null) { + await req.tenantPool.query( + `DELETE FROM auxiliar_supervisores WHERE auxiliar_user_id = $1`, + [userId], + ); + } else { + await req.tenantPool.query( + `INSERT INTO auxiliar_supervisores (auxiliar_user_id, supervisor_user_id) + VALUES ($1, $2) + ON CONFLICT (auxiliar_user_id) DO UPDATE SET supervisor_user_id = $2`, + [userId, supervisorUserId], + ); + } + res.json({ supervisorUserId }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +export async function deleteUsuario(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') { + throw new AppError(403, 'Solo los dueños pueden eliminar usuarios'); + } + const userId = req.params.id as string; + if (userId === req.user!.userId) { + throw new AppError(400, 'No puedes eliminar tu propia cuenta'); + } + await usuariosService.deleteUsuario(req.user!.tenantId, userId); + res.status(204).send(); + } catch (error) { + next(error); + } +} + +/** + * Actualiza un usuario globalmente (puede cambiar de empresa) + */ +export async function updateUsuarioGlobal(req: Request, res: Response, next: NextFunction) { + try { + if (!(await isGlobalAdmin(req))) { + throw new AppError(403, 'Solo el administrador global puede modificar usuarios globalmente'); + } + const userId = req.params.id as string; + const data = updateGlobalSchema.parse(req.body); + if (userId === req.user!.userId && data.tenantId) { + throw new AppError(400, 'No puedes cambiar tu propia empresa'); + } + const usuario = await usuariosService.updateUsuarioGlobal(userId, data); + res.json(usuario); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} + +/** + * Elimina un usuario globalmente + */ +export async function deleteUsuarioGlobal(req: Request, res: Response, next: NextFunction) { + try { + if (!(await isGlobalAdmin(req))) { + throw new AppError(403, 'Solo el administrador global puede eliminar usuarios globalmente'); + } + + const userId = req.params.id as string; + if (userId === req.user!.userId) { + throw new AppError(400, 'No puedes eliminar tu propia cuenta'); + } + await usuariosService.deleteUsuarioGlobal(userId); + res.status(204).send(); + } catch (error) { + next(error); + } +} + +/** + * Get cliente accesos (which contribuyentes a client user can access) + */ +export async function getClienteAccesos(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') throw new AppError(403, 'No autorizado'); + const userId = req.params.id as string; + const { rows } = await req.tenantPool!.query( + 'SELECT entidad_id AS "entidadId" FROM cliente_accesos WHERE user_id = $1', + [userId], + ); + res.json({ data: rows.map(r => r.entidadId) }); + } catch (error) { next(error); } +} + +/** + * Set cliente accesos (replace all accesos for a client user) + */ +export async function setClienteAccesos(req: Request, res: Response, next: NextFunction) { + try { + if (req.user!.role !== 'owner') throw new AppError(403, 'No autorizado'); + const userId = req.params.id as string; + const { entidadIds } = z.object({ + entidadIds: z.array(z.string().uuid()), + }).parse(req.body); + + // Replace all accesos + await req.tenantPool!.query('DELETE FROM cliente_accesos WHERE user_id = $1', [userId]); + for (const entidadId of entidadIds) { + await req.tenantPool!.query( + 'INSERT INTO cliente_accesos (user_id, entidad_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', + [userId, entidadId], + ); + } + res.json({ data: entidadIds }); + } catch (error) { + if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message)); + next(error); + } +} diff --git a/apps/api/src/controllers/webhook.controller.ts b/apps/api/src/controllers/webhook.controller.ts new file mode 100644 index 0000000..99187cc --- /dev/null +++ b/apps/api/src/controllers/webhook.controller.ts @@ -0,0 +1,244 @@ +import type { Request, Response, NextFunction } from 'express'; +import * as mpService from '../services/payment/mercadopago.service.js'; +import * as subscriptionService from '../services/payment/subscription.service.js'; +import * as invoicingService from '../services/payment/invoicing.service.js'; +import * as facturapiService from '../services/facturapi.service.js'; +import { handleAddonPayment } from '../services/payment/addon.service.js'; +import { prisma } from '../config/database.js'; +import { isDespachoPaidPlan } from '@horux/shared'; +import { despachoPlanTieneDualidadDb } from '../services/plan-catalogo.service.js'; +import { emailService } from '../services/email/email.service.js'; +import { getTenantOwnerEmail } from '../utils/memberships.js'; + +export async function handleMercadoPagoWebhook(req: Request, res: Response, next: NextFunction) { + try { + const { type, data } = req.body; + const xSignature = req.headers['x-signature'] as string; + const xRequestId = req.headers['x-request-id'] as string; + + // Verify webhook signature (mandatory) + if (!xSignature || !xRequestId || !data?.id) { + console.warn('[WEBHOOK] Missing signature headers'); + return res.status(401).json({ message: 'Missing signature headers' }); + } + + const isValid = mpService.verifyWebhookSignature(xSignature, xRequestId, String(data.id)); + if (!isValid) { + console.warn('[WEBHOOK] Invalid MercadoPago signature'); + return res.status(401).json({ message: 'Invalid signature' }); + } + + if (type === 'payment') { + await handlePaymentNotification(String(data.id)); + } else if (type === 'subscription_preapproval') { + await handlePreapprovalNotification(String(data.id)); + } + + // Always respond 200 to acknowledge receipt + res.status(200).json({ received: true }); + } catch (error) { + console.error('[WEBHOOK] Error processing MercadoPago webhook:', error); + // Still respond 200 to prevent retries for processing errors + res.status(200).json({ received: true, error: 'processing_error' }); + } +} + +async function handlePaymentNotification(paymentId: string) { + const payment = await mpService.getPaymentDetails(paymentId); + + if (!payment.externalReference) { + console.warn('[WEBHOOK] Payment without external_reference:', paymentId); + return; + } + + // Detecta compras de paquete de timbres. external_reference = `timbres-pack:{paymentId}` + if (payment.externalReference.startsWith('timbres-pack:')) { + const localPaymentId = payment.externalReference.split(':')[1]; + if (!localPaymentId) { + console.warn('[WEBHOOK] external_reference timbres-pack malformado:', payment.externalReference); + return; + } + + // Capturar estado previo para detectar transición (idempotencia: MP puede + // mandar el mismo webhook múltiples veces — solo notificamos en cambio real). + const before = await prisma.payment.findUnique({ + where: { id: localPaymentId }, + select: { status: true, tenantId: true, amount: true }, + }); + const previousStatus = before?.status ?? null; + + await prisma.payment.update({ + where: { id: localPaymentId }, + data: { + status: payment.status || 'unknown', + mpPaymentId: paymentId, + paidAt: payment.status === 'approved' ? new Date() : null, + }, + }); + + if (payment.status === 'approved') { + try { + await facturapiService.activarPaqueteTrasPago(localPaymentId); + } catch (error: any) { + console.error('[WEBHOOK] Error activando paquete de timbres:', error.message); + throw error; // que MP reintente + } + // Auto-emisión de factura (fail-soft) + await invoicingService.emitInvoiceIfApplicable(localPaymentId); + } else if ( + (payment.status === 'rejected' || payment.status === 'cancelled') && + previousStatus !== payment.status && + before + ) { + // Compra de paquete de timbres falló — el owner pagó y MP rechazó. Aviso fail-soft. + const tenant = await prisma.tenant.findUnique({ where: { id: before.tenantId }, select: { nombre: true } }); + const ownerEmail = await getTenantOwnerEmail(before.tenantId); + if (tenant && ownerEmail) { + emailService.sendPaymentFailed(ownerEmail, { + nombre: tenant.nombre, + amount: Number(before.amount), + plan: 'Paquete de timbres', + }).catch(err => console.error('[EMAIL] timbres-pack failed notification:', err)); + } + } + + if (typeof process.send === 'function') { + const pay = await prisma.payment.findUnique({ where: { id: localPaymentId }, select: { tenantId: true } }); + if (pay) process.send({ type: 'invalidate-tenant-cache', tenantId: pay.tenantId }); + } + return; + } + + // Detecta pagos de prorateo (upgrade). external_reference = `proration:${tenantId}:${subscriptionId}` + if (payment.externalReference.startsWith('proration:')) { + const parts = payment.externalReference.split(':'); + const tenantId = parts[1]; + const subscriptionId = parts[2]; + if (!tenantId || !subscriptionId) { + console.warn('[WEBHOOK] external_reference de proration malformado:', payment.externalReference); + return; + } + + const paymentRecord = await subscriptionService.recordPayment({ + tenantId, + subscriptionId, + mpPaymentId: paymentId, + amount: payment.transactionAmount || 0, + status: payment.status || 'unknown', + paymentMethod: `proration-${payment.paymentMethodId || 'unknown'}`, + }); + + if (payment.status === 'approved') { + try { + await subscriptionService.applyApprovedUpgrade(subscriptionId); + } catch (error: any) { + // Re-lanza para que MP reintente el webhook + console.error('[WEBHOOK] Error aplicando upgrade:', error.message); + throw error; + } + // Auto-emisión de factura (fail-soft, no bloquea ni tira) + await invoicingService.emitInvoiceIfApplicable(paymentRecord.id); + } + + if (typeof process.send === 'function') { + process.send({ type: 'invalidate-tenant-cache', tenantId }); + } + return; + } + + // Detecta pagos de addon. external_reference = `addon:{subscriptionAddonId}` + if (payment.externalReference.startsWith('addon:')) { + const addonId = payment.externalReference.replace('addon:', ''); + if (!addonId) { + console.warn('[WEBHOOK] external_reference addon malformado:', payment.externalReference); + return; + } + await handleAddonPayment(addonId, String(paymentId), payment.status || 'unknown'); + // Continue to normal flow only if we have a subscription to record against. + // Addon payments are fully handled by handleAddonPayment; no further action needed. + return; + } + + // Flujo normal: pago recurrente del preapproval + const tenantId = payment.externalReference; + const subscription = await prisma.subscription.findFirst({ + where: { tenantId }, + orderBy: { createdAt: 'desc' }, + }); + + if (!subscription) { + console.warn('[WEBHOOK] No subscription found for tenant:', tenantId); + return; + } + + const paymentRecord = await subscriptionService.recordPayment({ + tenantId, + subscriptionId: subscription.id, + mpPaymentId: paymentId, + amount: payment.transactionAmount || 0, + status: payment.status || 'unknown', + paymentMethod: payment.paymentMethodId || 'unknown', + }); + + if (payment.status === 'approved') { + // Transición pending → authorized es el momento del *primer* pago aprobado. + // En planes despacho con dualidad de precio (firstYear > renewal), bajamos + // el monto recurrente del preapproval para que las renovaciones cobren el + // precio de renewal. Se detecta comparando el monto cobrado contra lo que + // `getPlanPrice(phase='firstYear')` devolvería para este plan. + const esPrimerPago = subscription.status === 'pending'; + await prisma.subscription.update({ + where: { id: subscription.id }, + data: { status: 'authorized' }, + }); + subscriptionService.invalidateSubscriptionCache(tenantId); + + if ( + esPrimerPago && + subscription.mpPreapprovalId && + isDespachoPaidPlan(subscription.plan) && + await despachoPlanTieneDualidadDb(subscription.plan) + ) { + try { + const renewalAmount = await subscriptionService.getPlanPrice( + subscription.plan as any, + subscription.frequency as any, + 'renewal', + ); + await mpService.updatePreapprovalAmount(subscription.mpPreapprovalId, renewalAmount); + await prisma.subscription.update({ + where: { id: subscription.id }, + data: { amount: renewalAmount }, + }); + subscriptionService.invalidateSubscriptionCache(tenantId); + console.log(`[WEBHOOK] Preapproval ${subscription.mpPreapprovalId} bajado a $${renewalAmount} (renewal) tras primer pago`); + } catch (err: any) { + // No fallar el webhook — el cobro ya pasó. Logear para intervención manual. + console.error(`[WEBHOOK] Error bajando preapproval a renewal:`, err?.message || err); + } + } + + // Auto-emisión de factura (fail-soft, no bloquea ni tira) + await invoicingService.emitInvoiceIfApplicable(paymentRecord.id); + } + + if (typeof process.send === 'function') { + process.send({ type: 'invalidate-tenant-cache', tenantId }); + } +} + +async function handlePreapprovalNotification(preapprovalId: string) { + const preapproval = await mpService.getPreapproval(preapprovalId); + + if (preapproval.status) { + await subscriptionService.updateSubscriptionStatus(preapprovalId, preapproval.status); + } + + // Broadcast cache invalidation + const subscription = await prisma.subscription.findFirst({ + where: { mpPreapprovalId: preapprovalId }, + }); + if (subscription && typeof process.send === 'function') { + process.send({ type: 'invalidate-tenant-cache', tenantId: subscription.tenantId }); + } +} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts new file mode 100644 index 0000000..b4e136d --- /dev/null +++ b/apps/api/src/index.ts @@ -0,0 +1,57 @@ +import { app } from './app.js'; +import { env } from './config/env.js'; +import { tenantDb } from './config/database.js'; +import { invalidateTenantCache } from './middlewares/plan-limits.middleware.js'; +import { startSatSyncJob } from './jobs/sat-sync.job.js'; +import { startWeeklyUpdateJob } from './jobs/weekly-update.job.js'; +import { startMetricasInvalidationsJob } from './jobs/metricas-invalidations.job.js'; +import { startNotificationsJob } from './jobs/notifications.job.js'; + +const PORT = parseInt(env.PORT, 10); + +const server = app.listen(PORT, '0.0.0.0', () => { + console.log(`API Server running on http://0.0.0.0:${PORT}`); + console.log(`Environment: ${env.NODE_ENV}`); + + // Iniciar jobs programados. + // En dev, los crons se omiten por default para no consumir recursos ni + // disparar efectos (SAT queries, emails). Con ENABLE_CRONS_IN_DEV=1 se + // arrancan los crons seguros (SAT sync/retry, métricas); el weekly-update + // sigue siendo prod-only porque envía emails a owners reales. + const cronsEnabled = env.NODE_ENV === 'production' || process.env.ENABLE_CRONS_IN_DEV === '1'; + const sendRealEmails = env.NODE_ENV === 'production'; + if (cronsEnabled) { + startSatSyncJob(); + startMetricasInvalidationsJob(); + if (sendRealEmails) { + startWeeklyUpdateJob(); + startNotificationsJob(); + } else { + console.log('[Cron] weekly-update + notifications omitidos en dev (evita emails reales)'); + } + console.log(`[Cron] SAT + metricas activos (NODE_ENV=${env.NODE_ENV}, ENABLE_CRONS_IN_DEV=${process.env.ENABLE_CRONS_IN_DEV ?? 'unset'})`); + } else { + console.log('[Cron] Jobs omitidos en dev (usar ENABLE_CRONS_IN_DEV=1 para activar)'); + } +}); + +// Graceful shutdown — close all tenant DB pools before exiting +const gracefulShutdown = async (signal: string) => { + console.log(`${signal} received. Shutting down gracefully...`); + server.close(() => { + console.log('HTTP server closed'); + }); + await tenantDb.shutdown(); + process.exit(0); +}; + +process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); +process.on('SIGINT', () => gracefulShutdown('SIGINT')); + +// PM2 cluster: cross-worker cache invalidation +process.on('message', (msg: any) => { + if (msg?.type === 'invalidate-tenant-cache' && msg.tenantId) { + tenantDb.invalidatePool(msg.tenantId); + invalidateTenantCache(msg.tenantId); + } +}); diff --git a/apps/api/src/jobs/metricas-invalidations.job.ts b/apps/api/src/jobs/metricas-invalidations.job.ts new file mode 100644 index 0000000..e6cbd74 --- /dev/null +++ b/apps/api/src/jobs/metricas-invalidations.job.ts @@ -0,0 +1,53 @@ +import cron from 'node-cron'; +import { processAllTenantsInvalidations } from '../services/metricas-compute.service.js'; + +// Corre cada 15 minutos en dev/test para iteración rápida; en prod puedes +// espaciarlo a 30-60 min si el volumen de cambios es bajo. +const METRICAS_CRON_SCHEDULE = '*/15 * * * *'; + +let scheduledTask: ReturnType | null = null; +let running = false; // evita solapamiento si una corrida tarda más del intervalo + +async function runProcessInvalidations(): Promise { + if (running) { + console.log('[MetricasJob] Corrida previa aún en curso, skip'); + return; + } + running = true; + const start = Date.now(); + try { + const r = await processAllTenantsInvalidations(); + const elapsed = ((Date.now() - start) / 1000).toFixed(1); + if (r.totalProcesadas > 0 || r.totalErrores > 0) { + console.log( + `[MetricasJob] ${r.tenantsRevisados} tenants, ${r.totalProcesadas} invalidaciones procesadas, ` + + `${r.totalFilasEscritas} filas escritas, ${r.totalErrores} errores, ${elapsed}s`, + ); + } + } catch (err: any) { + console.error('[MetricasJob] Error fatal:', err?.message || err); + } finally { + running = false; + } +} + +export function startMetricasInvalidationsJob(): void { + if (scheduledTask) { + console.log('[MetricasJob] Job ya iniciado'); + return; + } + scheduledTask = cron.schedule(METRICAS_CRON_SCHEDULE, runProcessInvalidations, { + timezone: 'America/Mexico_City', + }); + console.log(`[MetricasJob] Cron iniciado (${METRICAS_CRON_SCHEDULE})`); +} + +export function stopMetricasInvalidationsJob(): void { + if (scheduledTask) { + scheduledTask.stop(); + scheduledTask = null; + } +} + +// Exportado para disparos manuales (ej. desde un endpoint admin) +export { runProcessInvalidations }; diff --git a/apps/api/src/jobs/notifications.job.ts b/apps/api/src/jobs/notifications.job.ts new file mode 100644 index 0000000..e444c0a --- /dev/null +++ b/apps/api/src/jobs/notifications.job.ts @@ -0,0 +1,104 @@ +/** + * Cron diario 8:30 AM (America/Mexico_City) que envía emails de: + * - Alertas fiscales nuevas (Option B — una sola vez por alerta). + * - Recordatorios próximos a vencer en ventanas 3d / 1d / 0d. + * + * Por-tenant try/catch: un fallo en un tenant no bloquea al resto. + */ +import cron from 'node-cron'; +import { prisma, tenantDb } from '../config/database.js'; +import { processNewAlertas, processProximosRecordatorios } from '../services/notifications.service.js'; + +const SCHEDULE = '30 8 * * *'; // 08:30 AM diario + +let task: ReturnType | null = null; + +/** Ejecuta ambos procesos para UN tenant. Exportado para disparo manual. */ +export async function runNotificationsForTenant(tenantId: string): Promise<{ + alertasNuevas: number; + recordatoriosEnviados: number; +}> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { id: true, nombre: true, rfc: true, active: true, databaseName: true }, + }); + if (!tenant || !tenant.active) { + return { alertasNuevas: 0, recordatoriosEnviados: 0 }; + } + + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + const ctx = { rfc: tenant.rfc, nombre: tenant.nombre }; + + const [alertasResult, recordResult] = await Promise.all([ + processNewAlertas(pool, tenantId, ctx).catch(err => { + console.error(`[Notifications] Alertas (${tenant.rfc}) fallo:`, err.message || err); + return { contribuyentes: 0, nuevasTotal: 0 }; + }), + processProximosRecordatorios(pool, tenantId, ctx).catch(err => { + console.error(`[Notifications] Recordatorios (${tenant.rfc}) fallo:`, err.message || err); + return { enviados: 0 }; + }), + ]); + + if (alertasResult.nuevasTotal > 0 || recordResult.enviados > 0) { + console.log(`[Notifications] ${tenant.rfc}: ${alertasResult.nuevasTotal} alertas nuevas, ${recordResult.enviados} recordatorios`); + } + + return { + alertasNuevas: alertasResult.nuevasTotal, + recordatoriosEnviados: recordResult.enviados, + }; +} + +/** Itera todos los tenants activos. */ +export async function runNotifications(): Promise<{ + tenants: number; + alertasNuevas: number; + recordatoriosEnviados: number; +}> { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true }, + }); + + let alertasNuevas = 0; + let recordatoriosEnviados = 0; + for (const t of tenants) { + try { + const r = await runNotificationsForTenant(t.id); + alertasNuevas += r.alertasNuevas; + recordatoriosEnviados += r.recordatoriosEnviados; + } catch (err: any) { + console.error(`[Notifications] Tenant ${t.rfc} fallo completo:`, err.message || err); + } + } + return { tenants: tenants.length, alertasNuevas, recordatoriosEnviados }; +} + +export function startNotificationsJob(): void { + if (task) { + console.warn('[Notifications Cron] Ya iniciado'); + return; + } + task = cron.schedule(SCHEDULE, async () => { + try { + const result = await runNotifications(); + console.log( + `[Notifications Cron] ${result.tenants} tenants — ` + + `${result.alertasNuevas} alertas nuevas, ${result.recordatoriosEnviados} recordatorios`, + ); + } catch (err: any) { + console.error('[Notifications Cron] Error general:', err.message || err); + } + }, { + timezone: 'America/Mexico_City', + }); + console.log(`[Notifications Cron] Programado: ${SCHEDULE} (08:30 AM diario America/Mexico_City)`); +} + +export function stopNotificationsJob(): void { + if (task) { + task.stop(); + task = null; + } +} diff --git a/apps/api/src/jobs/sat-sync.job.ts b/apps/api/src/jobs/sat-sync.job.ts new file mode 100644 index 0000000..441ec2b --- /dev/null +++ b/apps/api/src/jobs/sat-sync.job.ts @@ -0,0 +1,501 @@ +import cron from 'node-cron'; +import { prisma } from '../config/database.js'; +import { startSync, getSyncStatus, retryTimedOutJobs } from '../services/sat/sat.service.js'; +import { sweepStaleSatJobs } from '../services/sat/sweep-stale-jobs.service.js'; +import { hasFielConfigured } from '../services/fiel.service.js'; +import { consultarOpinion, limpiarOpinionesAntiguas } from '../services/opinion-cumplimiento.service.js'; +import { applyPendingChanges, expireTrials, sendExpiryReminders } from '../services/payment/subscription.service.js'; +import { resetExpiredMonthlyTimbres } from '../services/facturapi.service.js'; +import { purgeDeclaracionesAntiguas } from '../services/declaraciones.service.js'; +import { consultarConstancia, purgeConstanciasAntiguas } from '../services/constancia.service.js'; +import { tenantDb } from '../config/database.js'; + +const SYNC_CRON_SCHEDULE = '0 3 * * *'; // 3:00 AM todos los días +const CONCURRENT_SYNCS = 3; // Máximo de sincronizaciones simultáneas +const OPINION_CRON_SCHEDULE = '0 4 * * 0'; // Sundays 4:00 AM +const CSF_CRON_SCHEDULE = '0 4 1 * *'; // Día 1 de cada mes 04:00 AM (CSF mensual) +const INCREMENTAL_CRON_SCHEDULE = '0 11,15,19 * * *'; // 11:00, 15:00 y 19:00; fuera de ese rango el daily (03:00) cubre +const SUBSCRIPTION_LIFECYCLE_CRON = '30 2 * * *'; // 2:30 AM diario — aplica pending changes + expira trials +const EXPIRY_REMINDERS_CRON = '0 9 * * *'; // 9:00 AM diario — avisos pre-vencimiento (7d/3d/1d/0d) + +let isRunning = false; +let isIncrementalRunning = false; + +/** + * Obtiene los tenants que tienen FIEL configurada y activa + */ +async function getTenantsWithFiel(): Promise { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true }, + }); + + const tenantsWithFiel: string[] = []; + + for (const tenant of tenants) { + const hasFiel = await hasFielConfigured(tenant.id); + if (hasFiel) { + tenantsWithFiel.push(tenant.id); + } + } + + return tenantsWithFiel; +} + +/** + * Verifica si un tenant necesita sincronización inicial + */ +async function needsInitialSync(tenantId: string): Promise { + const completedSync = await prisma.satSyncJob.findFirst({ + where: { + tenantId, + type: 'initial', + status: 'completed', + }, + }); + + return !completedSync; +} + +/** + * Ejecuta sincronización para un tenant + */ +async function syncTenant(tenantId: string): Promise { + try { + // Verificar si hay sync activo + const status = await getSyncStatus(tenantId); + if (status.hasActiveSync) { + console.log(`[SAT Cron] Tenant ${tenantId} ya tiene sync activo, omitiendo`); + return; + } + + // Determinar tipo de sync + const needsInitial = await needsInitialSync(tenantId); + const syncType = needsInitial ? 'initial' : 'daily'; + + console.log(`[SAT Cron] Iniciando sync ${syncType} para tenant ${tenantId}`); + const jobId = await startSync(tenantId, syncType); + console.log(`[SAT Cron] Job ${jobId} iniciado para tenant ${tenantId}`); + } catch (error: any) { + console.error(`[SAT Cron] Error sincronizando tenant ${tenantId}:`, error.message); + } +} + +/** + * Ejecuta el job de sincronización para todos los tenants + */ +async function runSyncJob(): Promise { + if (isRunning) { + console.log('[SAT Cron] Job ya en ejecución, omitiendo'); + return; + } + + isRunning = true; + console.log('[SAT Cron] Iniciando job de sincronización diaria'); + + try { + const tenantIds = await getTenantsWithFiel(); + console.log(`[SAT Cron] ${tenantIds.length} tenants con FIEL configurada`); + + if (tenantIds.length === 0) { + console.log('[SAT Cron] No hay tenants para sincronizar'); + return; + } + + // Procesar en lotes para no saturar + for (let i = 0; i < tenantIds.length; i += CONCURRENT_SYNCS) { + const batch = tenantIds.slice(i, i + CONCURRENT_SYNCS); + await Promise.all(batch.map(syncTenant)); + + // Pequeña pausa entre lotes + if (i + CONCURRENT_SYNCS < tenantIds.length) { + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } + + console.log('[SAT Cron] Job de sincronización completado'); + } catch (error: any) { + console.error('[SAT Cron] Error en job:', error.message); + } finally { + isRunning = false; + } +} + +/** + * Obtiene los tenants activos cuyo plan habilita SAT incremental (3 syncs/día + * adicionales al daily). El flag vive en `despacho_plan_prices.permite_sat_incremental`, + * editable por admin global desde `/configuracion/precios-suscripcion`. + * Default backfill: mi_empresa_plus, business_control, business_cloud. + */ +async function getTenantsConSatIncremental(): Promise { + const planesIncrementales = await prisma.despachoPlanPrice.findMany({ + where: { permiteSatIncremental: true }, + select: { plan: true }, + }); + const planNames = planesIncrementales.map(p => p.plan); + if (planNames.length === 0) return []; + + const tenants = await prisma.tenant.findMany({ + where: { active: true, plan: { in: planNames as any } }, + select: { id: true }, + }); + + const result: string[] = []; + for (const tenant of tenants) { + if (await hasFielConfigured(tenant.id)) { + result.push(tenant.id); + } + } + return result; +} + +/** + * Dispara una sincronización incremental (ventana de 6 horas) para un tenant. + * Si el tenant ya tiene un sync activo, omite para no solapar solicitudes al SAT. + * Si el tenant nunca ha hecho `initial`, omite: el incremental no debe actuar + * como primera descarga — la inicial requiere correrse aparte. + */ +async function incrementalSyncTenant(tenantId: string): Promise { + try { + const status = await getSyncStatus(tenantId); + if (status.hasActiveSync) { + console.log(`[SAT Cron Inc] Tenant ${tenantId} con sync activo, omitiendo`); + return; + } + + const completedInitial = await prisma.satSyncJob.findFirst({ + where: { tenantId, type: 'initial', status: 'completed' }, + }); + if (!completedInitial) { + console.log(`[SAT Cron Inc] Tenant ${tenantId} sin sync inicial completado, omitiendo incremental`); + return; + } + + console.log(`[SAT Cron Inc] Iniciando incremental para tenant ${tenantId}`); + const jobId = await startSync(tenantId, 'incremental'); + console.log(`[SAT Cron Inc] Job ${jobId} iniciado`); + } catch (error: any) { + console.error(`[SAT Cron Inc] Error para tenant ${tenantId}:`, error.message); + } +} + +/** + * Ejecuta el job incremental para todos los tenants cuyo plan habilita SAT + * incremental (Mi Empresa +, Business Control, Enterprise por default; + * configurable desde admin via despacho_plan_prices.permite_sat_incremental). + */ +async function runIncrementalSyncJob(): Promise { + if (isIncrementalRunning) { + console.log('[SAT Cron Inc] Job ya en ejecución, omitiendo'); + return; + } + + isIncrementalRunning = true; + console.log('[SAT Cron Inc] Iniciando ciclo incremental'); + + try { + const tenantIds = await getTenantsConSatIncremental(); + console.log(`[SAT Cron Inc] ${tenantIds.length} tenants con incremental habilitado y FIEL`); + + if (tenantIds.length === 0) return; + + for (let i = 0; i < tenantIds.length; i += CONCURRENT_SYNCS) { + const batch = tenantIds.slice(i, i + CONCURRENT_SYNCS); + await Promise.all(batch.map(incrementalSyncTenant)); + + if (i + CONCURRENT_SYNCS < tenantIds.length) { + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } + + console.log('[SAT Cron Inc] Ciclo incremental completado'); + } catch (error: any) { + console.error('[SAT Cron Inc] Error en ciclo:', error.message); + } finally { + isIncrementalRunning = false; + } +} + +async function runOpinionJob(): Promise { + console.log('[Opinion Cron] Iniciando descarga semanal de Opinión de Cumplimiento'); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true, databaseName: true }, + }); + + let success = 0; + let failed = 0; + let skipped = 0; + + for (const tenant of tenants) { + const hasFiel = await hasFielConfigured(tenant.id); + if (!hasFiel) { + skipped++; + continue; + } + + try { + console.log(`[Opinion Cron] Consultando opinión para ${tenant.rfc}...`); + await consultarOpinion(tenant.id); + success++; + + // Cleanup old records + const pool = await tenantDb.getPool(tenant.id, tenant.databaseName); + const deleted = await limpiarOpinionesAntiguas(pool); + if (deleted > 0) { + console.log(`[Opinion Cron] ${tenant.rfc}: ${deleted} opiniones antiguas eliminadas`); + } + } catch (error: any) { + console.error(`[Opinion Cron] Error para ${tenant.rfc}:`, error.message); + failed++; + } + } + + console.log(`[Opinion Cron] Completado — éxito: ${success}, fallidos: ${failed}, sin FIEL: ${skipped}`); +} + +async function runCsfJob(): Promise { + console.log('[CSF Cron] Iniciando descarga mensual de Constancia de Situación Fiscal'); + + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true }, + }); + + let success = 0; + let failed = 0; + let skipped = 0; + + for (const tenant of tenants) { + const hasFiel = await hasFielConfigured(tenant.id); + if (!hasFiel) { skipped++; continue; } + try { + console.log(`[CSF Cron] Consultando CSF para ${tenant.rfc}...`); + await consultarConstancia(tenant.id); + success++; + } catch (error: any) { + console.error(`[CSF Cron] Error para ${tenant.rfc}:`, error.message); + failed++; + } + } + console.log(`[CSF Cron] Completado — éxito: ${success}, fallidos: ${failed}, sin FIEL: ${skipped}`); +} + +let scheduledTask: ReturnType | null = null; +let retryTask: ReturnType | null = null; +let opinionTask: ReturnType | null = null; +let csfTask: ReturnType | null = null; +let incrementalTask: ReturnType | null = null; +let subscriptionTask: ReturnType | null = null; +let expiryRemindersTask: ReturnType | null = null; +let watchdogTask: ReturnType | null = null; + +const RETRY_CRON_SCHEDULE = '0 * * * *'; // Cada hora +const WATCHDOG_CRON_SCHEDULE = '0 */2 * * *'; // Cada 2 horas — marca stale jobs como failed + +/** + * Inicia el job programado + */ +export function startSatSyncJob(): void { + if (scheduledTask) { + console.log('[SAT Cron] Job ya está programado'); + return; + } + + // Validar expresión cron + if (!cron.validate(SYNC_CRON_SCHEDULE)) { + console.error('[SAT Cron] Expresión cron inválida:', SYNC_CRON_SCHEDULE); + return; + } + + scheduledTask = cron.schedule(SYNC_CRON_SCHEDULE, runSyncJob, { + timezone: 'America/Mexico_City', + }); + + // Cron de reintentos: cada hora revisa si hay jobs pendientes de retry + retryTask = cron.schedule(RETRY_CRON_SCHEDULE, async () => { + try { + await retryTimedOutJobs(); + } catch (error: any) { + console.error('[SAT Retry Cron] Error:', error.message); + } + }, { + timezone: 'America/Mexico_City', + }); + + // Cron watchdog: cada 2h marca como `failed` los jobs que quedaron stale + // (pending con nextRetryAt > 12h atrás, running con startedAt > 4h atrás). + // Thresholds sobreescribibles vía env (STALE_PENDING_HOURS / STALE_RUNNING_HOURS) + // — defaults razonables pensando en que un sync inicial típico termina + // en <2h y el retryCron corre cada hora. + watchdogTask = cron.schedule(WATCHDOG_CRON_SCHEDULE, async () => { + try { + const pendingHours = Number(process.env.STALE_PENDING_HOURS || 12); + const runningHours = Number(process.env.STALE_RUNNING_HOURS || 4); + const result = await sweepStaleSatJobs({ apply: true, pendingHours, runningHours }); + if (result.pendingMarked + result.runningMarked > 0) { + console.log(`[SAT Watchdog] Marcados failed: pending=${result.pendingMarked} running=${result.runningMarked}`); + } + } catch (error: any) { + console.error('[SAT Watchdog] Error:', error.message); + } + }, { + timezone: 'America/Mexico_City', + }); + + opinionTask = cron.schedule(OPINION_CRON_SCHEDULE, async () => { + try { + await runOpinionJob(); + } catch (error: any) { + console.error('[Opinion Cron] Error:', error.message); + } + }, { + timezone: 'America/Mexico_City', + }); + + csfTask = cron.schedule(CSF_CRON_SCHEDULE, async () => { + try { + await runCsfJob(); + } catch (error: any) { + console.error('[CSF Cron] Error:', error.message); + } + }, { + timezone: 'America/Mexico_City', + }); + + incrementalTask = cron.schedule(INCREMENTAL_CRON_SCHEDULE, async () => { + try { + await runIncrementalSyncJob(); + } catch (error: any) { + console.error('[SAT Cron Inc] Error:', error.message); + } + }, { + timezone: 'America/Mexico_City', + }); + + subscriptionTask = cron.schedule(SUBSCRIPTION_LIFECYCLE_CRON, async () => { + try { + const pending = await applyPendingChanges(); + const trials = await expireTrials(); + // Reset mensual de TimbreSuscripcion: para cada tenant cuyo periodoFin + // ya pasó, resetea usados=0 y avanza la ventana +1 mes/año. Los paquetes + // adicionales NO se tocan; su expiraEn = adquiridoEn + 1 año fijo. + const timbres = await resetExpiredMonthlyTimbres(); + + // Cleanup retención 5 años (CFF Art. 30): borra declaraciones provisionales + // viejas. Iteramos cada tenant activo. Por-tenant try/catch para que un + // tenant que falla no bloquee al resto. + let declsBorradas = 0; + let csfsBorradas = 0; + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, databaseName: true, rfc: true }, + }); + for (const t of tenants) { + try { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const declResult = await purgeDeclaracionesAntiguas(pool); + declsBorradas += declResult.deleted; + const csfResult = await purgeConstanciasAntiguas(pool); + csfsBorradas += csfResult.deleted; + } catch (err: any) { + console.error(`[Cleanup] Tenant ${t.rfc} fallo en purge:`, err.message || err); + } + } + + console.log(`[Subscription Cron] pending aplicados: ${pending.applied} (${pending.errors} errores), trials expirados: ${trials.expired}, timbres reseteados: ${timbres.reset}, declaraciones >5 años borradas: ${declsBorradas}, CSFs >5 años borradas: ${csfsBorradas}`); + } catch (error: any) { + console.error('[Subscription Cron] Error:', error.message); + } + }, { + timezone: 'America/Mexico_City', + }); + + // Cron 9:00 AM diario — emails pre-vencimiento (7d/3d/1d) y aviso 0d post-vencimiento. + // Idempotente vía `Subscription.lastReminderDay`. + expiryRemindersTask = cron.schedule(EXPIRY_REMINDERS_CRON, async () => { + try { + const result = await sendExpiryReminders(); + if (result.sent > 0 || result.errors > 0) { + console.log(`[Expiry Reminders] enviados: ${result.sent}, reset por renovación: ${result.resetOnly}, skipped: ${result.skipped}, errores: ${result.errors}`); + } + } catch (error: any) { + console.error('[Expiry Reminders Cron] Error:', error.message); + } + }, { + timezone: 'America/Mexico_City', + }); + + console.log(`[SAT Cron] Job programado para: ${SYNC_CRON_SCHEDULE} (America/Mexico_City)`); + console.log(`[SAT Cron] Retry programado cada hora`); + console.log(`[Opinion Cron] Programado para: ${OPINION_CRON_SCHEDULE} (America/Mexico_City)`); + console.log(`[CSF Cron] Programado para: ${CSF_CRON_SCHEDULE} (America/Mexico_City)`); + console.log(`[SAT Cron Inc] Incremental Enterprise programado para: ${INCREMENTAL_CRON_SCHEDULE} (America/Mexico_City)`); + console.log(`[Subscription Cron] Lifecycle programado para: ${SUBSCRIPTION_LIFECYCLE_CRON} (America/Mexico_City)`); + console.log(`[SAT Watchdog] Programado para: ${WATCHDOG_CRON_SCHEDULE} (America/Mexico_City)`); +} + +/** + * Detiene el job programado + */ +export function stopSatSyncJob(): void { + if (scheduledTask) { + scheduledTask.stop(); + scheduledTask = null; + } + if (retryTask) { + retryTask.stop(); + retryTask = null; + } + if (opinionTask) { + opinionTask.stop(); + opinionTask = null; + } + if (csfTask) { + csfTask.stop(); + csfTask = null; + } + if (incrementalTask) { + incrementalTask.stop(); + incrementalTask = null; + } + if (subscriptionTask) { + subscriptionTask.stop(); + subscriptionTask = null; + } + if (expiryRemindersTask) { + expiryRemindersTask.stop(); + expiryRemindersTask = null; + } + if (watchdogTask) { + watchdogTask.stop(); + watchdogTask = null; + } + console.log('[SAT Cron] Jobs detenidos'); +} + +/** + * Ejecuta manualmente el ciclo incremental Enterprise (para testing). + */ +export async function runIncrementalSyncJobManually(): Promise { + await runIncrementalSyncJob(); +} + +/** + * Ejecuta el job manualmente (para testing o ejecución forzada) + */ +export async function runSatSyncJobManually(): Promise { + await runSyncJob(); +} + +/** + * Obtiene información del próximo job programado + */ +export function getJobInfo(): { scheduled: boolean; expression: string; timezone: string } { + return { + scheduled: scheduledTask !== null, + expression: SYNC_CRON_SCHEDULE, + timezone: 'America/Mexico_City', + }; +} diff --git a/apps/api/src/jobs/weekly-update.job.ts b/apps/api/src/jobs/weekly-update.job.ts new file mode 100644 index 0000000..6365d1c --- /dev/null +++ b/apps/api/src/jobs/weekly-update.job.ts @@ -0,0 +1,154 @@ +/** + * Cron Lunes 8:00 AM (America/Mexico_City) que envía a cada owner activo de + * cada tenant un correo "Actualización semanal" con KPIs del mes en curso + + * alertas automáticas + breakdown mensual de discrepancias de régimen. + * + * Manejo de errores: por-tenant try/catch — si falla uno, los demás siguen. + * Si SMTP no está configurado el email service logea a consola (dev), así + * que el job es seguro de correr en cualquier ambiente. + */ +import cron from 'node-cron'; +import { prisma } from '../config/database.js'; +import { tenantDb } from '../config/database.js'; +import { getKpis } from '../services/dashboard.service.js'; +import { generarAlertasAutomaticas, getDiscrepanciasPorMes } from '../services/alertas-auto.service.js'; +import { emailService } from '../services/email/email.service.js'; + +const SCHEDULE = '0 8 * * 1'; // Lunes 8:00 AM + +let task: ReturnType | null = null; + +function currentMonthRange(now = new Date()): { fechaInicio: string; fechaFin: string; periodoLabel: string } { + const año = now.getFullYear(); + const mes = now.getMonth(); + const inicio = new Date(año, mes, 1); + const fin = new Date(año, mes + 1, 0); + const NOMBRES = ['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre']; + return { + fechaInicio: inicio.toISOString().split('T')[0], + fechaFin: fin.toISOString().split('T')[0], + periodoLabel: `${NOMBRES[mes]} ${año}`, + }; +} + +/** + * Genera y envía el correo para UN tenant. Exportado para que pueda llamarse + * manualmente desde un endpoint admin (p.ej. "Enviar reporte ahora"). + */ +export async function sendWeeklyUpdateForTenant(tenantId: string): Promise<{ sent: number }> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { id: true, nombre: true, rfc: true, active: true, databaseName: true }, + }); + if (!tenant || !tenant.active) { + console.log(`[Weekly] Tenant ${tenantId} no encontrado o inactivo, skip`); + return { sent: 0 }; + } + + // Recipientes: owners activos del tenant + const owners = await prisma.tenantMembership.findMany({ + where: { tenantId, isOwner: true, active: true }, + include: { user: { select: { email: true, nombre: true, active: true } } }, + }); + const recipients = owners.filter(o => o.user.active); + if (recipients.length === 0) { + console.log(`[Weekly] Tenant ${tenant.rfc} sin owners activos, skip`); + return { sent: 0 }; + } + + // Pool del tenant para queries de CFDI + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + + const { fechaInicio, fechaFin, periodoLabel } = currentMonthRange(); + + // Ejecuta los 3 colectores en paralelo + const [kpis, alertas, discrepanciasPorMes] = await Promise.all([ + getKpis(pool, fechaInicio, fechaFin, tenant.id, false), + generarAlertasAutomaticas(pool, tenant.id), + getDiscrepanciasPorMes(pool, tenant.id, 6), + ]); + + const fechaGeneracion = new Date().toLocaleString('es-MX', { + dateStyle: 'long', + timeStyle: 'short', + timeZone: 'America/Mexico_City', + }); + + let sent = 0; + for (const r of recipients) { + try { + await emailService.sendWeeklyUpdate(r.user.email, { + nombre: r.user.nombre, + empresa: tenant.nombre, + periodoLabel, + kpis: { + ingresos: kpis.ingresos, + egresos: kpis.egresos, + utilidad: kpis.utilidad, + margen: kpis.margen, + ivaBalance: kpis.ivaBalance, + ivaAFavorAcumulado: kpis.ivaAFavorAcumulado, + cfdisEmitidos: kpis.cfdisEmitidos, + cfdisRecibidos: kpis.cfdisRecibidos, + }, + alertas: alertas.map(a => ({ titulo: a.titulo, mensaje: a.mensaje, prioridad: a.prioridad })), + discrepanciasPorMes: discrepanciasPorMes.map(d => ({ label: d.label, count: d.count })), + fechaGeneracion, + }); + sent++; + } catch (err: any) { + console.error(`[Weekly] Error enviando a ${r.user.email} (tenant ${tenant.rfc}):`, err.message || err); + } + } + + console.log(`[Weekly] Tenant ${tenant.rfc}: ${sent}/${recipients.length} correos enviados`); + return { sent }; +} + +/** + * Itera todos los tenants activos y dispara `sendWeeklyUpdateForTenant`. + * Por-tenant try/catch para que un fallo no bloquee al resto. + */ +export async function runWeeklyUpdate(): Promise<{ tenants: number; emails: number }> { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, rfc: true }, + }); + + let totalEmails = 0; + for (const t of tenants) { + try { + const { sent } = await sendWeeklyUpdateForTenant(t.id); + totalEmails += sent; + } catch (err: any) { + console.error(`[Weekly] Tenant ${t.rfc} fallo completo:`, err.message || err); + } + } + + return { tenants: tenants.length, emails: totalEmails }; +} + +export function startWeeklyUpdateJob(): void { + if (task) { + console.warn('[Weekly Cron] Ya iniciado'); + return; + } + task = cron.schedule(SCHEDULE, async () => { + try { + const result = await runWeeklyUpdate(); + console.log(`[Weekly Cron] Reporte enviado: ${result.emails} correos a ${result.tenants} tenants`); + } catch (err: any) { + console.error('[Weekly Cron] Error general:', err.message || err); + } + }, { + timezone: 'America/Mexico_City', + }); + console.log(`[Weekly Cron] Programado: ${SCHEDULE} (Lunes 8:00 AM America/Mexico_City)`); +} + +export function stopWeeklyUpdateJob(): void { + if (task) { + task.stop(); + task = null; + } +} diff --git a/apps/api/src/middlewares/auth.middleware.ts b/apps/api/src/middlewares/auth.middleware.ts new file mode 100644 index 0000000..79c7348 --- /dev/null +++ b/apps/api/src/middlewares/auth.middleware.ts @@ -0,0 +1,112 @@ +import type { Request, Response, NextFunction } from 'express'; +import { verifyToken } from '../auth/tokens.js'; +import { AppError } from './error.middleware.js'; +import { prisma } from '../config/database.js'; +import type { JWTPayload, Role } from '@horux/shared'; + +declare global { + namespace Express { + interface Request { + user?: JWTPayload; + } + } +} + +/** + * Cache de `tokenVersion` por userId con TTL 30s. Evita hit a BD en cada + * request autenticada. Al incrementar `tokenVersion` (password change, + * logout-all), se llama `invalidateTokenVersionCache(userId)` que borra la + * entrada y broadcast entre workers PM2. + */ +const tokenVersionCache = new Map(); +const TOKEN_VERSION_TTL_MS = 30 * 1000; + +export function invalidateTokenVersionCache(userId: string) { + tokenVersionCache.delete(userId); + if (typeof process.send === 'function') { + process.send({ type: 'invalidate-token-version', userId }); + } +} + +// Escucha broadcasts entre workers PM2 cluster +if (typeof process.on === 'function') { + process.on('message', (msg: any) => { + if (msg && msg.type === 'invalidate-token-version' && typeof msg.userId === 'string') { + tokenVersionCache.delete(msg.userId); + } + }); +} + +async function getCurrentTokenVersion(userId: string): Promise { + const cached = tokenVersionCache.get(userId); + if (cached && cached.expires > Date.now()) return cached.version; + + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { tokenVersion: true, active: true }, + }); + if (!user || !user.active) return null; // User borrado o desactivado → rechaza + + tokenVersionCache.set(userId, { + version: user.tokenVersion, + expires: Date.now() + TOKEN_VERSION_TTL_MS, + }); + return user.tokenVersion; +} + +export async function authenticate(req: Request, res: Response, next: NextFunction) { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return next(new AppError(401, 'Token no proporcionado')); + } + + const token = authHeader.split(' ')[1]; + + let payload: JWTPayload; + try { + payload = verifyToken(token); + } catch (error) { + return next(new AppError(401, 'Token inválido o expirado')); + } + + // Check tokenVersion contra BD. JWT con versión menor a la actual del user + // quedan rechazados (forzando re-login tras password change / logout-all). + const currentVersion = await getCurrentTokenVersion(payload.userId); + if (currentVersion === null) { + return next(new AppError(401, 'Sesión inválida. Vuelve a iniciar sesión.')); + } + const jwtVersion = payload.tokenVersion ?? 0; + if (jwtVersion !== currentVersion) { + return next(new AppError(401, 'Sesión expirada. Por seguridad, inicia sesión de nuevo.')); + } + + req.user = payload; + next(); +} + +// Roles superset de plataforma: bypassean cualquier `authorize(...)` porque +// son staff transversal de Horux 360. Coincide con `SUPERSET_ROLES` en +// utils/platform-admin.ts (mantener sincronizado). +const PLATFORM_SUPERSET = new Set(['platform_admin', 'platform_ti']); + +export function authorize(...roles: Role[]) { + return (req: Request, res: Response, next: NextFunction) => { + if (!req.user) { + return next(new AppError(401, 'No autenticado')); + } + + // Platform admin/TI bypass: son superrol y deben poder hitear cualquier + // endpoint protegido por rol de tenant. (Documentado en CLAUDE.md → + // "platform_admin y platform_ti son supersets — implican todos los demás".) + const platformRoles = req.user.platformRoles || []; + const hasSuperset = platformRoles.some(r => PLATFORM_SUPERSET.has(r)); + if (hasSuperset) return next(); + + if (roles.length > 0 && !roles.includes(req.user.role)) { + return next(new AppError(403, 'No autorizado')); + } + + next(); + }; +} diff --git a/apps/api/src/middlewares/error.middleware.ts b/apps/api/src/middlewares/error.middleware.ts new file mode 100644 index 0000000..9ec2382 --- /dev/null +++ b/apps/api/src/middlewares/error.middleware.ts @@ -0,0 +1,33 @@ +import type { Request, Response, NextFunction } from 'express'; + +export class AppError extends Error { + constructor( + public statusCode: number, + public message: string, + public isOperational = true + ) { + super(message); + Object.setPrototypeOf(this, AppError.prototype); + } +} + +export function errorMiddleware( + err: Error, + req: Request, + res: Response, + next: NextFunction +) { + if (err instanceof AppError) { + return res.status(err.statusCode).json({ + status: 'error', + message: err.message, + }); + } + + console.error('Unhandled error:', err); + + return res.status(500).json({ + status: 'error', + message: 'Internal server error', + }); +} diff --git a/apps/api/src/middlewares/feature-gate.middleware.ts b/apps/api/src/middlewares/feature-gate.middleware.ts new file mode 100644 index 0000000..487f29c --- /dev/null +++ b/apps/api/src/middlewares/feature-gate.middleware.ts @@ -0,0 +1,41 @@ +import type { Request, Response, NextFunction } from 'express'; +import { hasDespachoFeature, type DespachoPlan } from '@horux/shared'; +import { prisma } from '../config/database.js'; + +const planCache = new Map(); + +/** + * Middleware factory that gates routes based on tenant plan features. + * Usage: requireFeature('reportes') — blocks access if tenant's plan lacks the feature. + * + * Tras eliminar Horux 360 legacy, todos los planes son del catálogo despacho. + * Un plan desconocido (no en DESPACHO_PLANS) hace que `hasDespachoFeature` + * retorne false → 403, lo cual es el comportamiento defensivo correcto. + */ +export function requireFeature(feature: string) { + return async (req: Request, res: Response, next: NextFunction) => { + if (!req.user) return res.status(401).json({ message: 'No autenticado' }); + + let plan: string; + const cached = planCache.get(req.user.tenantId); + if (cached && cached.expires > Date.now()) { + plan = cached.plan; + } else { + const tenant = await prisma.tenant.findUnique({ + where: { id: req.user.tenantId }, + select: { plan: true }, + }); + if (!tenant) return res.status(404).json({ message: 'Tenant no encontrado' }); + plan = tenant.plan; + planCache.set(req.user.tenantId, { plan, expires: Date.now() + 5 * 60 * 1000 }); + } + + if (!hasDespachoFeature(plan as DespachoPlan, feature)) { + return res.status(403).json({ + message: 'Tu plan no incluye esta función. Contacta soporte para upgrade.', + }); + } + + next(); + }; +} diff --git a/apps/api/src/middlewares/plan-limits.middleware.ts b/apps/api/src/middlewares/plan-limits.middleware.ts new file mode 100644 index 0000000..efe5703 --- /dev/null +++ b/apps/api/src/middlewares/plan-limits.middleware.ts @@ -0,0 +1,81 @@ +import type { Request, Response, NextFunction } from 'express'; +import { prisma } from '../config/database.js'; +import { isGlobalAdmin } from '../utils/global-admin.js'; +import { getSubscriptionState } from '@horux/shared'; + +// Simple in-memory cache with TTL +const cache = new Map(); + +async function getCached(key: string, ttlMs: number, fetcher: () => Promise): Promise { + const entry = cache.get(key); + if (entry && entry.expires > Date.now()) return entry.data; + const data = await fetcher(); + cache.set(key, { data, expires: Date.now() + ttlMs }); + return data; +} + +export function invalidateTenantCache(tenantId: string) { + for (const key of cache.keys()) { + if (key.includes(tenantId)) cache.delete(key); + } +} + +/** + * Verifica que el tenant tenga una suscripción que permita escritura. + * GETs siempre pasan (modo lectura preserva acceso histórico para auditorías y export). + * + * La autoridad de "puede escribir" está en `getSubscriptionState().needsRenewal` + * (helper compartido entre frontend y backend) — combina `status` + `currentPeriodEnd`, + * por eso un trial cuyo período ya pasó pero el cron de las 02:30 AM aún no lo ha + * marcado `trial_expired` queda bloqueado igual. + */ +export async function checkPlanLimits(req: Request, res: Response, next: NextFunction) { + if (!req.user) return next(); + + // Allow GET requests siempre — modo lectura habilitado incluso sin suscripción + // activa (preserva acceso a dashboard/CFDIs históricos para auditorías). + if (req.method === 'GET') return next(); + + // Global admin impersonation bypasses subscription check + if (req.headers['x-view-tenant'] && await isGlobalAdmin(req.user.tenantId, req.user.role)) { + return next(); + } + + const subscription = await getCached( + `sub:${req.user.tenantId}`, + 5 * 60 * 1000, + () => prisma.subscription.findFirst({ + where: { tenantId: req.user!.tenantId }, + orderBy: { createdAt: 'desc' }, + }) + ); + + const state = getSubscriptionState(subscription); + + if (state.needsRenewal) { + return res.status(403).json({ + code: 'SUBSCRIPTION_INACTIVE', + message: state.isTrialExpired + ? 'Tu prueba gratuita terminó. Renueva tu plan para continuar.' + : state.isCancelledExpired + ? 'Tu suscripción venció. Renueva tu plan para continuar.' + : 'No tienes una suscripción activa. Contrata un plan para continuar.', + redirectTo: '/configuracion/planes-despacho', + subscriptionStatus: state.status, + }); + } + + next(); +} + +/** + * No-op tras eliminar el catálogo Horux 360 legacy. En el modelo despacho el + * límite de CFDIs es por contribuyente (`DESPACHO_PLANS[plan].maxCfdisPorContribuyente`, + * 1M-3M), no por tenant — tan alto que no tiene sentido enforced en cada + * insert. Si en el futuro se quiere gating real por contribuyente, debe + * leerse del catálogo despacho con conocimiento del contribuyente_id que + * está creando el CFDI (no disponible en este middleware tenant-level). + */ +export async function checkCfdiLimit(_req: Request, _res: Response, next: NextFunction) { + return next(); +} diff --git a/apps/api/src/middlewares/rate-limit.middleware.ts b/apps/api/src/middlewares/rate-limit.middleware.ts new file mode 100644 index 0000000..1a47616 --- /dev/null +++ b/apps/api/src/middlewares/rate-limit.middleware.ts @@ -0,0 +1,92 @@ +import rateLimit, { ipKeyGenerator, type Options } from 'express-rate-limit'; +import type { Request } from 'express'; +import { hasPlatformRole } from '../utils/platform-admin.js'; + +/** + * Rate limiting por endpoint con 4 tiers según sensibilidad / costo computacional. + * + * Todas las keys se generan por `userId` (no IP) — usuarios legítimos detrás de NAT + * compartido (ej: oficina con 20 contadores) no se bloquean entre sí. + * + * Admin global (tenant dueño de la plataforma o platform_admin/platform_ti) está exento: + * necesita hacer operaciones masivas ocasionalmente (backfill, corrección manual, etc). + */ + +const keyByUser = (req: Request): string => { + // User autenticado → rate-limit por userId. Anónimo → por IP normalizada con + // ipKeyGenerator (maneja IPv6 correctamente). Sin esto, express-rate-limit + // emite warning de potential bypass con IPv6 mal truncado. + if (req.user?.userId) return req.user.userId; + return ipKeyGenerator(req.ip || 'anonymous'); +}; + +const skipForGlobalAdmin = async (req: Request): Promise => { + if (!req.user?.userId) return false; + try { + // hasPlatformRole(..., 'platform_admin') retorna true para superset (admin o TI). + // Otros platform roles (support/sales/finance) sí respetan rate limits. + return await hasPlatformRole(req.user.userId, 'platform_admin'); + } catch { + return false; + } +}; + +const baseConfig: Partial = { + keyGenerator: keyByUser, + standardHeaders: true, + legacyHeaders: false, + skip: skipForGlobalAdmin, +}; + +/** + * Tier más estricto — 2 requests por día. + * Para operaciones que disparan syncs largos con el SAT o cómputo muy pesado + * (Playwright headless contra portal SAT). + */ +export const veryStrictLimit = rateLimit({ + ...baseConfig, + windowMs: 24 * 60 * 60 * 1000, + max: 2, + message: { + message: 'Has alcanzado el límite de esta operación (2 por día). Intenta mañana o contacta soporte si es urgente.', + }, +}); + +/** + * Tier estricto — 10 requests por hora. + * Operaciones costosas o con side-effects en terceros (Facturapi, MP). + */ +export const strictLimit = rateLimit({ + ...baseConfig, + windowMs: 60 * 60 * 1000, + max: 10, + message: { + message: 'Demasiadas solicitudes en esta operación. Intenta de nuevo en una hora.', + }, +}); + +/** + * Tier normal — 100 requests por 15 min. + * APIs de negocio (dashboard, cfdi list, reportes, cálculos fiscales). + */ +export const normalLimit = rateLimit({ + ...baseConfig, + windowMs: 15 * 60 * 1000, + max: 100, + message: { + message: 'Demasiadas solicitudes. Espera unos minutos e intenta de nuevo.', + }, +}); + +/** + * Tier relajado — 500 requests por 15 min. + * Endpoints de lectura barata (catálogos SAT, listas fijas). + */ +export const relaxedLimit = rateLimit({ + ...baseConfig, + windowMs: 15 * 60 * 1000, + max: 500, + message: { + message: 'Demasiadas solicitudes. Espera unos minutos e intenta de nuevo.', + }, +}); diff --git a/apps/api/src/middlewares/tenant.middleware.ts b/apps/api/src/middlewares/tenant.middleware.ts new file mode 100644 index 0000000..8ed5c81 --- /dev/null +++ b/apps/api/src/middlewares/tenant.middleware.ts @@ -0,0 +1,144 @@ +import type { Request, Response, NextFunction } from 'express'; +import type { Pool } from 'pg'; +import { prisma, tenantDb } from '../config/database.js'; +import { isGlobalAdmin } from '../utils/global-admin.js'; +import { decryptAesGcm, deriveAesKey } from '@horux/core'; +import { env } from '../config/env.js'; + +declare global { + namespace Express { + interface Request { + tenantPool?: Pool; + viewingTenantId?: string; + } + } +} + +// Cache: tenantId -> { databaseName, expires } +// Only used for MANAGED tenants. BYO tenants always query Prisma so connection info stays fresh. +const tenantDbCache = new Map(); +const CACHE_TTL = 5 * 60 * 1000; // 5 minutes + +type ConnectionOverride = { host: string; port: number; user: string; password: string }; + +/** + * Decrypt the BYO connection string stored in the central DB. + * Returns a connection override object, or null if decryption fails. + */ +async function resolveBYOConnection( + dbConnectionEnc: string, + dbConnectionIv: string, + tenantId: string, +): Promise { + try { + const encKey = env.CONNECTOR_ENCRYPTION_KEY + ? deriveAesKey(env.CONNECTOR_ENCRYPTION_KEY) + : deriveAesKey(env.FIEL_ENCRYPTION_KEY); + + const encData = Buffer.from(dbConnectionEnc, 'base64'); + const iv = Buffer.from(dbConnectionIv, 'base64'); + // AES-GCM: last 16 bytes of the ciphertext blob are the auth tag + const authTag = encData.subarray(encData.length - 16); + const ciphertext = encData.subarray(0, encData.length - 16); + + const decrypted = decryptAesGcm(ciphertext, iv, authTag, encKey); + const config = JSON.parse(decrypted.toString('utf-8')); + + return { + host: config.host, + port: config.port ?? 5432, + user: config.user, + password: config.password, + }; + } catch (err) { + console.error(`[TenantMiddleware] BYO decrypt failed for tenant ${tenantId}:`, err); + return null; + } +} + +export function invalidateTenantDbCache(tenantId: string) { + tenantDbCache.delete(tenantId); +} + +export async function tenantMiddleware(req: Request, res: Response, next: NextFunction) { + try { + if (!req.user) { + return res.status(401).json({ message: 'No autenticado' }); + } + + let tenantId = req.user.tenantId; + + // Admin impersonation via X-View-Tenant header (global admin only) + const viewTenantHeader = req.headers['x-view-tenant'] as string; + if (viewTenantHeader) { + const globalAdmin = await isGlobalAdmin(req.user.tenantId, req.user.role); + if (!globalAdmin) { + return res.status(403).json({ message: 'No autorizado para ver otros tenants' }); + } + + const viewedTenant = await prisma.tenant.findFirst({ + where: { + OR: [ + { id: viewTenantHeader }, + { rfc: viewTenantHeader }, + ], + }, + select: { id: true, databaseName: true, active: true, dbMode: true, dbConnectionEnc: true, dbConnectionIv: true }, + }); + + if (!viewedTenant) { + return res.status(404).json({ message: 'Tenant no encontrado' }); + } + + if (!viewedTenant.active) { + return res.status(403).json({ message: 'Tenant inactivo' }); + } + + tenantId = viewedTenant.id; + req.viewingTenantId = viewedTenant.id; + + let impersonateOverride: ConnectionOverride | undefined; + if (viewedTenant.dbMode === 'BYO' && viewedTenant.dbConnectionEnc && viewedTenant.dbConnectionIv) { + const override = await resolveBYOConnection(viewedTenant.dbConnectionEnc, viewedTenant.dbConnectionIv, tenantId); + if (!override) { + return res.status(503).json({ message: 'Base de datos del despacho no disponible. Verifica tu connector.' }); + } + impersonateOverride = override; + } + + req.tenantPool = await tenantDb.getPool(tenantId, viewedTenant.databaseName, impersonateOverride); + return next(); + } + + // Normal flow: query tenant details including BYO fields. + // BYO tenants bypass the databaseName cache so connection info stays fresh. + // The 5-min idle eviction in TenantConnectionManager still protects against excessive pool creation. + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { databaseName: true, dbMode: true, dbConnectionEnc: true, dbConnectionIv: true }, + }); + + if (!tenant?.databaseName) { + return res.status(404).json({ message: 'Tenant no encontrado' }); + } + + let connectionOverride: ConnectionOverride | undefined; + + if (tenant.dbMode === 'BYO' && tenant.dbConnectionEnc && tenant.dbConnectionIv) { + const override = await resolveBYOConnection(tenant.dbConnectionEnc, tenant.dbConnectionIv, tenantId); + if (!override) { + return res.status(503).json({ message: 'Base de datos del despacho no disponible. Verifica tu connector.' }); + } + connectionOverride = override; + } else { + // MANAGED: keep the databaseName cache warm to avoid repeated Prisma queries + tenantDbCache.set(tenantId, { databaseName: tenant.databaseName, expires: Date.now() + CACHE_TTL }); + } + + req.tenantPool = await tenantDb.getPool(tenantId, tenant.databaseName, connectionOverride); + next(); + } catch (error) { + console.error('[TenantMiddleware] Error:', error); + return res.status(500).json({ message: 'Error al resolver tenant' }); + } +} diff --git a/apps/api/src/migrations/tenant/001_initial_schema.sql b/apps/api/src/migrations/tenant/001_initial_schema.sql new file mode 100644 index 0000000..76cde40 --- /dev/null +++ b/apps/api/src/migrations/tenant/001_initial_schema.sql @@ -0,0 +1,244 @@ +-- Migration: 001_initial_schema +-- Description: Full initial DDL for tenant databases (horux_) +-- Created: 2026-04-13 +-- Tables: rfcs, bancos, cfdis, cfdi_conceptos, conciliaciones, alertas, recordatorios + +-- Extensions +CREATE EXTENSION IF NOT EXISTS pg_trgm; + +-- Tables + +CREATE TABLE IF NOT EXISTS rfcs ( + id SERIAL PRIMARY KEY, + rfc VARCHAR(14) UNIQUE NOT NULL, + razon_social VARCHAR(255), + regimen_fiscal VARCHAR(3), + codigo_postal VARCHAR(5) +); + +CREATE TABLE IF NOT EXISTS bancos ( + id SERIAL PRIMARY KEY, + banco VARCHAR(100) NOT NULL, + terminacion_cuenta VARCHAR(4) NOT NULL, + creado_en TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS cfdis ( + id SERIAL PRIMARY KEY, + year VARCHAR(4), + month VARCHAR(2), + type VARCHAR(10), + uuid VARCHAR(36) UNIQUE, + serie VARCHAR(50), + folio VARCHAR(50), + status VARCHAR(20), + fecha_emision TIMESTAMP, + rfc_emisor_id INTEGER REFERENCES rfcs(id), + rfc_emisor VARCHAR(13), + nombre_emisor VARCHAR(255), + rfc_receptor_id INTEGER REFERENCES rfcs(id), + rfc_receptor VARCHAR(13), + nombre_receptor VARCHAR(255), + subtotal NUMERIC(18,4), + subtotal_mxn NUMERIC(18,4), + descuento NUMERIC(18,4), + descuento_mxn NUMERIC(18,4), + total NUMERIC(18,4), + total_mxn NUMERIC(18,4), + saldo_insoluto TEXT, + moneda VARCHAR(3), + tipo_cambio NUMERIC(18,6), + tipo_comprobante VARCHAR(1), + metodo_pago VARCHAR(3), + forma_pago VARCHAR(2), + uso_cfdi VARCHAR(5), + pac VARCHAR(13), + fecha_cert_sat TIMESTAMP, + fecha_cancelacion TIMESTAMP, + uuid_relacionado TEXT, + isr_retencion NUMERIC(18,4), + isr_retencion_mxn NUMERIC(18,4), + iva_traslado NUMERIC(18,4), + iva_traslado_mxn NUMERIC(18,4), + iva_retencion NUMERIC(18,4), + iva_retencion_mxn NUMERIC(18,4), + ieps_traslado NUMERIC(18,4), + ieps_traslado_mxn NUMERIC(18,4), + ieps_retencion NUMERIC(18,4), + ieps_retencion_mxn NUMERIC(18,4), + impuestos_locales_trasladado NUMERIC(18,4), + impuestos_locales_trasladado_mxn NUMERIC(18,4), + impuestos_locales_retenidos NUMERIC(18,4), + impuestos_locales_retenidos_mxn NUMERIC(18,4), + monto_pago NUMERIC(18,4), + monto_pago_mxn NUMERIC(18,4), + fecha_pago_p TIMESTAMP, + num_parcialidad TEXT, + isr_retencion_pago NUMERIC(18,4), + isr_retencion_pago_mxn NUMERIC(18,4), + iva_traslado_pago NUMERIC(18,4), + iva_traslado_pago_mxn NUMERIC(18,4), + iva_retencion_pago NUMERIC(18,4), + iva_retencion_pago_mxn NUMERIC(18,4), + ieps_traslado_pago NUMERIC(18,4), + ieps_traslado_pago_mxn NUMERIC(18,4), + ieps_retencion_pago NUMERIC(18,4), + ieps_retencion_pago_mxn NUMERIC(18,4), + saldo_pendiente NUMERIC(18,4), + saldo_pendiente_mxn NUMERIC(18,4), + fecha_liquidacion TIMESTAMP, + fecha_pago DATE, + fecha_inicial_pago DATE, + fecha_final_pago DATE, + num_dias_pagados NUMERIC(10,2), + num_seguro_social VARCHAR(50), + puesto VARCHAR(255), + salario_base_cot_apor NUMERIC(18,4), + salario_base_cot_apor_mxn NUMERIC(18,4), + salario_diario_integrado NUMERIC(18,4), + salario_diario_integrado_mxn NUMERIC(18,4), + total_percepciones NUMERIC(18,4), + total_percepciones_mxn NUMERIC(18,4), + total_deducciones NUMERIC(18,4), + total_deducciones_mxn NUMERIC(18,4), + imp_retenidos_nomina NUMERIC(18,4), + imp_retenidos_nomina_mxn NUMERIC(18,4), + otras_deducciones_nomina NUMERIC(18,4), + otras_deducciones_nomina_mxn NUMERIC(18,4), + subsidio_causado NUMERIC(18,4), + subsidio_causado_mxn NUMERIC(18,4), + conciliado VARCHAR(50), + id_conciliacion INTEGER, + xml_url TEXT, + pdf_url TEXT, + xml_original TEXT, + last_sat_sync TIMESTAMP, + sat_sync_job_id UUID, + source VARCHAR(20) DEFAULT 'manual', + facturapi_id VARCHAR(50), + regimen_fiscal_emisor VARCHAR(3), + regimen_fiscal_receptor VARCHAR(3), + creado_en TIMESTAMP DEFAULT NOW(), + actualizado_en TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS cfdi_conceptos ( + id SERIAL PRIMARY KEY, + cfdi_id INTEGER REFERENCES cfdis(id) ON DELETE CASCADE, + clave_prod_serv VARCHAR(10), + no_identificacion VARCHAR(100), + descripcion TEXT, + cantidad NUMERIC(18,4), + clave_unidad VARCHAR(10), + unidad VARCHAR(100), + valor_unitario NUMERIC(18,4), + valor_unitario_mxn NUMERIC(18,4), + importe NUMERIC(18,4), + importe_mxn NUMERIC(18,4), + descuento NUMERIC(18,4), + descuento_mxn NUMERIC(18,4), + isr_retencion NUMERIC(18,4), + isr_retencion_mxn NUMERIC(18,4), + iva_traslado NUMERIC(18,4), + iva_traslado_mxn NUMERIC(18,4), + iva_retencion NUMERIC(18,4), + iva_retencion_mxn NUMERIC(18,4), + ieps_traslado NUMERIC(18,4), + ieps_traslado_mxn NUMERIC(18,4), + ieps_retencion NUMERIC(18,4), + ieps_retencion_mxn NUMERIC(18,4), + impuestos_locales_trasladado NUMERIC(18,4), + impuestos_locales_trasladado_mxn NUMERIC(18,4), + impuestos_locales_retenidos NUMERIC(18,4), + impuestos_locales_retenidos_mxn NUMERIC(18,4), + total_percepciones NUMERIC(18,4), + total_percepciones_mxn NUMERIC(18,4), + total_deducciones NUMERIC(18,4), + total_deducciones_mxn NUMERIC(18,4), + imp_retenidos_nomina NUMERIC(18,4), + imp_retenidos_nomina_mxn NUMERIC(18,4), + otras_deducciones_nomina NUMERIC(18,4), + otras_deducciones_nomina_mxn NUMERIC(18,4), + subsidio_causado NUMERIC(18,4), + subsidio_causado_mxn NUMERIC(18,4), + creado_en TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS conciliaciones ( + id SERIAL PRIMARY KEY, + anio VARCHAR(4) NOT NULL, + mes VARCHAR(2) NOT NULL, + id_cfdi INTEGER NOT NULL UNIQUE REFERENCES cfdis(id), + fecha_de_pago DATE NOT NULL, + id_banco INTEGER NOT NULL REFERENCES bancos(id), + creado_en TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS alertas ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tipo VARCHAR(50) NOT NULL, + titulo VARCHAR(200) NOT NULL, + mensaje TEXT, + prioridad VARCHAR(20) DEFAULT 'media', + fecha_vencimiento TIMESTAMP, + leida BOOLEAN DEFAULT FALSE, + resuelta BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS recordatorios ( + id SERIAL PRIMARY KEY, + titulo VARCHAR(200) NOT NULL, + descripcion TEXT, + fecha_limite DATE NOT NULL, + notas TEXT, + completado BOOLEAN DEFAULT FALSE, + privado BOOLEAN DEFAULT FALSE, + creado_por UUID NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- ============================================= +-- Columns that may be missing on older tenants +-- (CREATE TABLE IF NOT EXISTS won't add these if the table already existed) +-- ============================================= + +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS id_conciliacion INTEGER; +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS conciliado VARCHAR(50); +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS source VARCHAR(20) DEFAULT 'manual'; +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS facturapi_id VARCHAR(50); +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS regimen_fiscal_emisor VARCHAR(3); +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS regimen_fiscal_receptor VARCHAR(3); +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS last_sat_sync TIMESTAMP; +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS sat_sync_job_id UUID; + +-- Indexes + +CREATE INDEX IF NOT EXISTS idx_cfdis_fecha_emision ON cfdis(fecha_emision DESC); +CREATE INDEX IF NOT EXISTS idx_cfdis_type ON cfdis(type); +CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_emisor ON cfdis(rfc_emisor); +CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_receptor ON cfdis(rfc_receptor); +CREATE INDEX IF NOT EXISTS idx_cfdis_status ON cfdis(status); +CREATE INDEX IF NOT EXISTS idx_cfdis_year_month ON cfdis(year, month); +CREATE INDEX IF NOT EXISTS idx_cfdis_nombre_emisor_trgm ON cfdis USING gin(nombre_emisor gin_trgm_ops); +CREATE INDEX IF NOT EXISTS idx_cfdis_nombre_receptor_trgm ON cfdis USING gin(nombre_receptor gin_trgm_ops); + +CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_emisor_id ON cfdis(rfc_emisor_id); +CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_receptor_id ON cfdis(rfc_receptor_id); + +CREATE INDEX IF NOT EXISTS idx_cfdi_conceptos_cfdi_id ON cfdi_conceptos(cfdi_id); +CREATE INDEX IF NOT EXISTS idx_cfdi_conceptos_clave ON cfdi_conceptos(clave_prod_serv); + +CREATE INDEX IF NOT EXISTS idx_conciliaciones_anio_mes ON conciliaciones(anio, mes); +CREATE INDEX IF NOT EXISTS idx_conciliaciones_id_cfdi ON conciliaciones(id_cfdi); +CREATE INDEX IF NOT EXISTS idx_cfdis_id_conciliacion ON cfdis(id_conciliacion); + +-- Deferred FK: cfdis.id_conciliacion -> conciliaciones(id) +-- (cfdis is created before conciliaciones, so this constraint is added after both tables exist) + +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'cfdis_id_conciliacion_fkey') THEN + ALTER TABLE cfdis ADD CONSTRAINT cfdis_id_conciliacion_fkey FOREIGN KEY (id_conciliacion) REFERENCES conciliaciones(id); + END IF; +END $$; diff --git a/apps/api/src/migrations/tenant/002_create_opiniones_cumplimiento.sql b/apps/api/src/migrations/tenant/002_create_opiniones_cumplimiento.sql new file mode 100644 index 0000000..bf86509 --- /dev/null +++ b/apps/api/src/migrations/tenant/002_create_opiniones_cumplimiento.sql @@ -0,0 +1,16 @@ +-- 002_create_opiniones_cumplimiento +-- Table for storing SAT Opinión de Cumplimiento PDFs and metadata + +CREATE TABLE IF NOT EXISTS opiniones_cumplimiento ( + id SERIAL PRIMARY KEY, + rfc VARCHAR(14) NOT NULL, + razon_social VARCHAR(255), + estatus VARCHAR(50) NOT NULL, + folio VARCHAR(50), + cadena_original TEXT, + fecha_consulta TIMESTAMP NOT NULL, + pdf BYTEA NOT NULL, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_opiniones_fecha ON opiniones_cumplimiento(fecha_consulta DESC); diff --git a/apps/api/src/migrations/tenant/003_create_declaraciones_provisionales.sql b/apps/api/src/migrations/tenant/003_create_declaraciones_provisionales.sql new file mode 100644 index 0000000..17b2701 --- /dev/null +++ b/apps/api/src/migrations/tenant/003_create_declaraciones_provisionales.sql @@ -0,0 +1,36 @@ +-- Declaraciones provisionales del tenant: PDFs subidos por el contador con +-- el comprobante de la declaración + opcionalmente el comprobante de pago. +-- Al subir una declaración o un comprobante, el sistema marca como resueltas +-- las alertas correspondientes (decl-XXX o pago-XXX) en la tabla `alertas`. +-- +-- Reglas: +-- - 1 declaración tipo='normal' por (año, mes) — UNIQUE parcial +-- - N declaraciones tipo='complementaria' por (año, mes) — sin restricción +-- - `impuestos` es un array de strings: ['IVA', 'ISR', 'IEPS', etc.] que +-- cubre la declaración. Permite saber qué alertas resolver. + +CREATE TABLE IF NOT EXISTS declaraciones_provisionales ( + id SERIAL PRIMARY KEY, + año INT NOT NULL, + mes INT NOT NULL CHECK (mes BETWEEN 1 AND 12), + tipo VARCHAR(15) NOT NULL CHECK (tipo IN ('normal', 'complementaria')), + impuestos TEXT[] NOT NULL, -- ['IVA', 'ISR', 'IEPS', ...] + pdf_declaracion BYTEA NOT NULL, + pdf_filename VARCHAR(255), + link_pago TEXT, + pdf_pago BYTEA, + pdf_pago_filename VARCHAR(255), + pagado_at TIMESTAMP, + creado_por VARCHAR(255), -- email del user que la subió + notas TEXT, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_declaraciones_periodo ON declaraciones_provisionales(año DESC, mes DESC); + +-- Solo 1 declaración tipo='normal' por (año, mes). Las complementarias no +-- tienen restricción de cantidad. +CREATE UNIQUE INDEX IF NOT EXISTS uniq_declaracion_normal_mes + ON declaraciones_provisionales(año, mes) + WHERE tipo = 'normal'; diff --git a/apps/api/src/migrations/tenant/004_declaraciones_liga_pago_pdf.sql b/apps/api/src/migrations/tenant/004_declaraciones_liga_pago_pdf.sql new file mode 100644 index 0000000..7eecbc7 --- /dev/null +++ b/apps/api/src/migrations/tenant/004_declaraciones_liga_pago_pdf.sql @@ -0,0 +1,11 @@ +-- La "liga de pago" de la declaración es un PDF (no un URL). Reemplazamos +-- la columna TEXT por un par BYTEA+filename, consistente con pdf_declaracion +-- y pdf_pago. Si la migración 003 aún no se desplegó en algún ambiente, +-- este ALTER aplica igual (DROP IF EXISTS + ADD COLUMN IF NOT EXISTS). + +ALTER TABLE declaraciones_provisionales + DROP COLUMN IF EXISTS link_pago; + +ALTER TABLE declaraciones_provisionales + ADD COLUMN IF NOT EXISTS pdf_liga_pago BYTEA, + ADD COLUMN IF NOT EXISTS pdf_liga_pago_filename VARCHAR(255); diff --git a/apps/api/src/migrations/tenant/005_create_constancias_situacion_fiscal.sql b/apps/api/src/migrations/tenant/005_create_constancias_situacion_fiscal.sql new file mode 100644 index 0000000..5ac1d01 --- /dev/null +++ b/apps/api/src/migrations/tenant/005_create_constancias_situacion_fiscal.sql @@ -0,0 +1,24 @@ +-- Constancia de Situación Fiscal: PDF descargado del portal SAT con Playwright +-- + FIEL. Se descarga automáticamente el 1° de cada mes y al primer upload de +-- FIEL del tenant. Retención 5 años (similar a declaraciones_provisionales). +-- +-- `datos` es un JSONB con el shape `ConstanciaSituacionFiscal` del prototipo +-- (domicilio, régimenes activos, actividades, obligaciones, sellos). Se +-- guarda completo para poder re-hidratar la UI sin re-parsear el PDF, y +-- comparar entre consultas (detectar cambios de domicilio/régimen). + +CREATE TABLE IF NOT EXISTS constancias_situacion_fiscal ( + id SERIAL PRIMARY KEY, + rfc VARCHAR(13) NOT NULL, + id_cif VARCHAR(20), + razon_social TEXT, + estatus_padron VARCHAR(30), + fecha_emision TEXT, -- "GUADALAJARA, JALISCO A 14 DE ABRIL DE 2026" (formato libre del SAT) + datos JSONB NOT NULL, -- shape ConstanciaSituacionFiscal completo + pdf BYTEA NOT NULL, + fecha_consulta TIMESTAMP DEFAULT NOW(), + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_csf_fecha_consulta + ON constancias_situacion_fiscal(fecha_consulta DESC); diff --git a/apps/api/src/migrations/tenant/006_tenant_migrations_tracking.sql b/apps/api/src/migrations/tenant/006_tenant_migrations_tracking.sql new file mode 100644 index 0000000..71003f1 --- /dev/null +++ b/apps/api/src/migrations/tenant/006_tenant_migrations_tracking.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS tenant_migrations ( + scope varchar(50) NOT NULL, + version int NOT NULL, + name varchar(255), + applied_at timestamptz DEFAULT now(), + PRIMARY KEY (scope, version) +); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES + ('legacy', 1, '001_initial_schema'), + ('legacy', 2, '002_create_opiniones_cumplimiento'), + ('legacy', 3, '003_create_declaraciones_provisionales'), + ('legacy', 4, '004_declaraciones_liga_pago_pdf'), + ('legacy', 5, '005_create_constancias_situacion_fiscal'), + ('legacy', 6, '006_tenant_migrations_tracking') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/007_entidades_gestionadas.sql b/apps/api/src/migrations/tenant/007_entidades_gestionadas.sql new file mode 100644 index 0000000..ee1e685 --- /dev/null +++ b/apps/api/src/migrations/tenant/007_entidades_gestionadas.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS entidades_gestionadas ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + tipo varchar(20) NOT NULL, + nombre text NOT NULL, + identificador text, + supervisor_user_id uuid, + active boolean DEFAULT true, + created_at timestamptz DEFAULT now(), + updated_at timestamptz DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS ix_entidades_supervisor ON entidades_gestionadas(supervisor_user_id); +CREATE INDEX IF NOT EXISTS ix_entidades_tipo ON entidades_gestionadas(tipo, active); +CREATE INDEX IF NOT EXISTS ix_entidades_identificador ON entidades_gestionadas(identificador); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('core', 7, '007_entidades_gestionadas') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/008_carteras.sql b/apps/api/src/migrations/tenant/008_carteras.sql new file mode 100644 index 0000000..ef73b15 --- /dev/null +++ b/apps/api/src/migrations/tenant/008_carteras.sql @@ -0,0 +1,27 @@ +CREATE TABLE IF NOT EXISTS carteras ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + supervisor_user_id uuid NOT NULL, + nombre text NOT NULL, + descripcion text, + created_at timestamptz DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS ix_carteras_supervisor ON carteras(supervisor_user_id); + +CREATE TABLE IF NOT EXISTS cartera_entidades ( + cartera_id uuid NOT NULL REFERENCES carteras(id) ON DELETE CASCADE, + entidad_id uuid NOT NULL REFERENCES entidades_gestionadas(id) ON DELETE CASCADE, + added_at timestamptz DEFAULT now(), + PRIMARY KEY (cartera_id, entidad_id) +); + +CREATE TABLE IF NOT EXISTS cartera_auxiliares ( + cartera_id uuid NOT NULL REFERENCES carteras(id) ON DELETE CASCADE, + auxiliar_user_id uuid NOT NULL, + added_at timestamptz DEFAULT now(), + PRIMARY KEY (cartera_id, auxiliar_user_id) +); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('core', 8, '008_carteras') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/009_cliente_accesos.sql b/apps/api/src/migrations/tenant/009_cliente_accesos.sql new file mode 100644 index 0000000..d28672a --- /dev/null +++ b/apps/api/src/migrations/tenant/009_cliente_accesos.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS cliente_accesos ( + user_id uuid NOT NULL, + entidad_id uuid NOT NULL REFERENCES entidades_gestionadas(id) ON DELETE CASCADE, + granted_at timestamptz DEFAULT now(), + PRIMARY KEY (user_id, entidad_id) +); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('core', 9, '009_cliente_accesos') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/010_contribuyentes.sql b/apps/api/src/migrations/tenant/010_contribuyentes.sql new file mode 100644 index 0000000..3c9fd02 --- /dev/null +++ b/apps/api/src/migrations/tenant/010_contribuyentes.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS contribuyentes ( + entidad_id uuid PRIMARY KEY REFERENCES entidades_gestionadas(id) ON DELETE CASCADE, + rfc varchar(13) NOT NULL UNIQUE, + regimen_fiscal varchar(3), + codigo_postal varchar(5), + domicilio jsonb +); + +CREATE INDEX IF NOT EXISTS ix_contribuyentes_rfc ON contribuyentes(rfc); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 10, '010_contribuyentes') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/011_fiel_per_contribuyente.sql b/apps/api/src/migrations/tenant/011_fiel_per_contribuyente.sql new file mode 100644 index 0000000..35e0269 --- /dev/null +++ b/apps/api/src/migrations/tenant/011_fiel_per_contribuyente.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS fiel_contribuyente ( + contribuyente_id uuid PRIMARY KEY REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + rfc varchar(13) NOT NULL, + cer_data bytea NOT NULL, + key_data bytea NOT NULL, + key_password_enc bytea NOT NULL, + cer_iv bytea NOT NULL, + cer_tag bytea NOT NULL, + key_iv bytea NOT NULL, + key_tag bytea NOT NULL, + password_iv bytea NOT NULL, + password_tag bytea NOT NULL, + serial_number varchar(50), + valid_from timestamptz NOT NULL, + valid_until timestamptz NOT NULL, + is_active boolean DEFAULT true, + uploaded_at timestamptz DEFAULT now(), + updated_at timestamptz DEFAULT now() +); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 11, '011_fiel_per_contribuyente') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/012_facturapi_per_contribuyente.sql b/apps/api/src/migrations/tenant/012_facturapi_per_contribuyente.sql new file mode 100644 index 0000000..e310dc2 --- /dev/null +++ b/apps/api/src/migrations/tenant/012_facturapi_per_contribuyente.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS facturapi_orgs ( + contribuyente_id uuid PRIMARY KEY REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + facturapi_org_id text NOT NULL UNIQUE, + csd_uploaded boolean DEFAULT false, + active boolean DEFAULT true, + created_at timestamptz DEFAULT now() +); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 12, '012_facturapi_per_contribuyente') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/013_cfdi_contribuyente_id.sql b/apps/api/src/migrations/tenant/013_cfdi_contribuyente_id.sql new file mode 100644 index 0000000..b6bab8b --- /dev/null +++ b/apps/api/src/migrations/tenant/013_cfdi_contribuyente_id.sql @@ -0,0 +1,7 @@ +ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS contribuyente_id uuid REFERENCES contribuyentes(entidad_id); + +CREATE INDEX IF NOT EXISTS ix_cfdi_contribuyente ON cfdis(contribuyente_id) WHERE contribuyente_id IS NOT NULL; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 13, '013_cfdi_contribuyente_id') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/014_metricas_mensuales.sql b/apps/api/src/migrations/tenant/014_metricas_mensuales.sql new file mode 100644 index 0000000..cb372d0 --- /dev/null +++ b/apps/api/src/migrations/tenant/014_metricas_mensuales.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS metricas_mensuales ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + anio smallint NOT NULL, + mes smallint NOT NULL, + regimen_fiscal varchar(3), + formula_version smallint DEFAULT 1, + iva_trasladado_16 numeric(18,2) DEFAULT 0, + iva_trasladado_8 numeric(18,2) DEFAULT 0, + iva_trasladado_0 numeric(18,2) DEFAULT 0, + iva_trasladado_exento numeric(18,2) DEFAULT 0, + iva_trasladado_total numeric(18,2) DEFAULT 0, + iva_acreditable numeric(18,2) DEFAULT 0, + iva_retenido_cobrado numeric(18,2) DEFAULT 0, + iva_retenido_pagado numeric(18,2) DEFAULT 0, + iva_resultado numeric(18,2) DEFAULT 0, + iva_a_favor_mes numeric(18,2) DEFAULT 0, + isr_ingresos_brutos numeric(18,2) DEFAULT 0, + isr_deducciones_autoriz numeric(18,2) DEFAULT 0, + isr_base numeric(18,2) DEFAULT 0, + isr_causado numeric(18,2) DEFAULT 0, + isr_retenido numeric(18,2) DEFAULT 0, + isr_a_pagar numeric(18,2) DEFAULT 0, + ieps_trasladado numeric(18,2) DEFAULT 0, + ieps_acreditable numeric(18,2) DEFAULT 0, + cfdis_emitidos_count int DEFAULT 0, + cfdis_recibidos_count int DEFAULT 0, + cfdis_cancelados_count int DEFAULT 0, + ingresos_devengados numeric(18,2) DEFAULT 0, + ingresos_cobrados numeric(18,2) DEFAULT 0, + egresos_devengados numeric(18,2) DEFAULT 0, + egresos_pagados numeric(18,2) DEFAULT 0, + utilidad_devengada numeric(18,2) DEFAULT 0, + utilidad_realizada numeric(18,2) DEFAULT 0, + flujo_entradas numeric(18,2) DEFAULT 0, + flujo_salidas numeric(18,2) DEFAULT 0, + flujo_neto numeric(18,2) DEFAULT 0, + cxc_saldo_final numeric(18,2) DEFAULT 0, + cxp_saldo_final numeric(18,2) DEFAULT 0, + cxc_cfdis_count int DEFAULT 0, + cxp_cfdis_count int DEFAULT 0, + cerrado boolean DEFAULT false, + computed_at timestamptz DEFAULT now(), + source_max_cfdi_at timestamptz, + UNIQUE (contribuyente_id, anio, mes, regimen_fiscal) +); +CREATE INDEX IF NOT EXISTS ix_metricas_contrib_anio ON metricas_mensuales(contribuyente_id, anio DESC, mes DESC); +CREATE INDEX IF NOT EXISTS ix_metricas_cerrado ON metricas_mensuales(cerrado, computed_at); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 14, '014_metricas_mensuales') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/015_metricas_acumuladas_anuales.sql b/apps/api/src/migrations/tenant/015_metricas_acumuladas_anuales.sql new file mode 100644 index 0000000..f7be484 --- /dev/null +++ b/apps/api/src/migrations/tenant/015_metricas_acumuladas_anuales.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS metricas_acumuladas_anuales ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + anio smallint NOT NULL, + regimen_fiscal varchar(3), + formula_version smallint DEFAULT 1, + iva_a_favor_arrastrado numeric(18,2) DEFAULT 0, + iva_a_favor_generado numeric(18,2) DEFAULT 0, + iva_a_favor_aplicado numeric(18,2) DEFAULT 0, + iva_a_favor_saldo numeric(18,2) DEFAULT 0, + ingresos_anuales numeric(18,2) DEFAULT 0, + deducciones_anuales numeric(18,2) DEFAULT 0, + utilidad_anual numeric(18,2) DEFAULT 0, + isr_causado_anual numeric(18,2) DEFAULT 0, + isr_retenido_anual numeric(18,2) DEFAULT 0, + isr_a_pagar_anual numeric(18,2) DEFAULT 0, + cerrado boolean DEFAULT false, + computed_at timestamptz DEFAULT now(), + UNIQUE (contribuyente_id, anio, regimen_fiscal) +); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 15, '015_metricas_acumuladas_anuales') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/016_metricas_por_contraparte_anuales.sql b/apps/api/src/migrations/tenant/016_metricas_por_contraparte_anuales.sql new file mode 100644 index 0000000..6d6ff14 --- /dev/null +++ b/apps/api/src/migrations/tenant/016_metricas_por_contraparte_anuales.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS metricas_por_contraparte_anuales ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + anio smallint NOT NULL, + rfc_contraparte varchar(13) NOT NULL, + nombre_contraparte text, + tipo char(1), + subtotal numeric(18,2), + total numeric(18,2), + cfdis_count int, + concentracion_pct numeric(5,2), + computed_at timestamptz DEFAULT now(), + UNIQUE (contribuyente_id, anio, rfc_contraparte, tipo) +); +CREATE INDEX IF NOT EXISTS ix_metricas_contraparte_top ON metricas_por_contraparte_anuales(contribuyente_id, anio, tipo, total DESC); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 16, '016_metricas_por_contraparte_anuales') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/017_metricas_invalidaciones.sql b/apps/api/src/migrations/tenant/017_metricas_invalidaciones.sql new file mode 100644 index 0000000..ed9d79c --- /dev/null +++ b/apps/api/src/migrations/tenant/017_metricas_invalidaciones.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS metricas_invalidaciones ( + contribuyente_id uuid NOT NULL, + anio smallint NOT NULL, + mes smallint NOT NULL, + reason text, + marcado_at timestamptz DEFAULT now(), + PRIMARY KEY (contribuyente_id, anio, mes) +); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 17, '017_metricas_invalidaciones') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/018_obligaciones_contribuyente.sql b/apps/api/src/migrations/tenant/018_obligaciones_contribuyente.sql new file mode 100644 index 0000000..4d77847 --- /dev/null +++ b/apps/api/src/migrations/tenant/018_obligaciones_contribuyente.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS obligaciones_contribuyente ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + catalogo_id text, + nombre text NOT NULL, + fundamento text, + frecuencia text, + fecha_limite text, + categoria text, + activa boolean DEFAULT true, + es_recomendada boolean DEFAULT false, + es_custom boolean DEFAULT false, + created_at timestamptz DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS ix_obligaciones_contrib ON obligaciones_contribuyente(contribuyente_id, activa); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 18, '018_obligaciones_contribuyente') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/019_obligaciones_completada.sql b/apps/api/src/migrations/tenant/019_obligaciones_completada.sql new file mode 100644 index 0000000..059ce1d --- /dev/null +++ b/apps/api/src/migrations/tenant/019_obligaciones_completada.sql @@ -0,0 +1,8 @@ +ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS completada boolean DEFAULT false; +ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS completada_at timestamptz; +ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS completada_por uuid; +ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS periodo_completado varchar(7); -- "2026-04" (year-month) + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 19, '019_obligaciones_completada') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/020_obligacion_periodos.sql b/apps/api/src/migrations/tenant/020_obligacion_periodos.sql new file mode 100644 index 0000000..7982cbb --- /dev/null +++ b/apps/api/src/migrations/tenant/020_obligacion_periodos.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS obligacion_periodos ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + obligacion_id uuid NOT NULL REFERENCES obligaciones_contribuyente(id) ON DELETE CASCADE, + periodo varchar(7) NOT NULL, + completada boolean DEFAULT false, + completada_at timestamptz, + completada_por uuid, + notas text, + created_at timestamptz DEFAULT now(), + UNIQUE (obligacion_id, periodo) +); + +CREATE INDEX IF NOT EXISTS ix_obligacion_periodos_periodo ON obligacion_periodos(periodo); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 20, '020_obligacion_periodos') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/021_declaraciones_periodicidad_monto.sql b/apps/api/src/migrations/tenant/021_declaraciones_periodicidad_monto.sql new file mode 100644 index 0000000..b9a623e --- /dev/null +++ b/apps/api/src/migrations/tenant/021_declaraciones_periodicidad_monto.sql @@ -0,0 +1,15 @@ +-- Add periodicidad (period type) and monto_pago (payment amount) to declaraciones. +-- periodicidad replaces the assumption that all declarations are monthly. +-- monto_pago = 0 means the declaration results in $0 to pay (auto-mark as paid). + +ALTER TABLE declaraciones_provisionales + ADD COLUMN IF NOT EXISTS periodicidad VARCHAR(15) NOT NULL DEFAULT 'mensual' + CHECK (periodicidad IN ('mensual', 'bimestral', 'trimestral', 'semestral', 'anual')); + +ALTER TABLE declaraciones_provisionales + ADD COLUMN IF NOT EXISTS monto_pago NUMERIC(15,2); + +-- For existing rows that already have a payment proof, backfill pagado_at if null +UPDATE declaraciones_provisionales +SET pagado_at = updated_at +WHERE pdf_pago IS NOT NULL AND pagado_at IS NULL; diff --git a/apps/api/src/migrations/tenant/022_carteras_subcarteras.sql b/apps/api/src/migrations/tenant/022_carteras_subcarteras.sql new file mode 100644 index 0000000..037b8cf --- /dev/null +++ b/apps/api/src/migrations/tenant/022_carteras_subcarteras.sql @@ -0,0 +1,23 @@ +-- Subcarteras: a cartera can be a child of another cartera. +-- Top-level carteras belong to a supervisor (or owner). +-- Subcarteras belong to an auxiliar within a parent cartera. + +ALTER TABLE carteras + ADD COLUMN IF NOT EXISTS parent_id uuid REFERENCES carteras(id) ON DELETE CASCADE; + +ALTER TABLE carteras + ADD COLUMN IF NOT EXISTS auxiliar_user_id uuid; + +-- Allow supervisor_user_id to be NULL for subcarteras (inherited from parent) +ALTER TABLE carteras + ALTER COLUMN supervisor_user_id DROP NOT NULL; + +CREATE INDEX IF NOT EXISTS ix_carteras_parent ON carteras(parent_id); +CREATE INDEX IF NOT EXISTS ix_carteras_auxiliar ON carteras(auxiliar_user_id); + +-- Track which supervisor an auxiliar reports to (1:1 per auxiliar) +CREATE TABLE IF NOT EXISTS auxiliar_supervisores ( + auxiliar_user_id uuid NOT NULL PRIMARY KEY, + supervisor_user_id uuid NOT NULL, + created_at timestamptz DEFAULT now() +); diff --git a/apps/api/src/migrations/tenant/023_bancos_contribuyente.sql b/apps/api/src/migrations/tenant/023_bancos_contribuyente.sql new file mode 100644 index 0000000..fc89b23 --- /dev/null +++ b/apps/api/src/migrations/tenant/023_bancos_contribuyente.sql @@ -0,0 +1,4 @@ +-- Bancos belong to individual contribuyentes, not the whole tenant. +-- Used for conciliación per-contribuyente. +ALTER TABLE bancos ADD COLUMN IF NOT EXISTS contribuyente_id uuid; +CREATE INDEX IF NOT EXISTS ix_bancos_contribuyente ON bancos(contribuyente_id); diff --git a/apps/api/src/migrations/tenant/024_cfdi_descartados.sql b/apps/api/src/migrations/tenant/024_cfdi_descartados.sql new file mode 100644 index 0000000..4f839da --- /dev/null +++ b/apps/api/src/migrations/tenant/024_cfdi_descartados.sql @@ -0,0 +1,12 @@ +-- CFDIs descartados de alertas (ej: discrepancias de régimen que el usuario revisó y decidió ignorar). +-- El descarte se aplica por tipo de alerta y cfdi_id. +CREATE TABLE IF NOT EXISTS cfdi_descartados ( + id serial PRIMARY KEY, + cfdi_id integer NOT NULL, + tipo_alerta text NOT NULL, -- e.g. 'discrepancia-regimen' + descartado_por text, -- email or userId + created_at timestamptz DEFAULT now(), + UNIQUE (cfdi_id, tipo_alerta) +); + +CREATE INDEX IF NOT EXISTS ix_cfdi_descartados_tipo ON cfdi_descartados(tipo_alerta); diff --git a/apps/api/src/migrations/tenant/025_contribuyentes_regimen_fiscal_text.sql b/apps/api/src/migrations/tenant/025_contribuyentes_regimen_fiscal_text.sql new file mode 100644 index 0000000..a0c79b6 --- /dev/null +++ b/apps/api/src/migrations/tenant/025_contribuyentes_regimen_fiscal_text.sql @@ -0,0 +1,18 @@ +-- Amplía contribuyentes.regimen_fiscal a TEXT para soportar CSV de múltiples +-- regímenes (ej. "626,605"). La migración 010 original lo declaró varchar(3) +-- asumiendo un solo régimen por contribuyente, pero el código sincroniza CSV +-- desde la CSF (sincronizarDatosFiscales en constancia.service.ts). +-- +-- Síntoma antes del fix: el sync falla con "el valor es demasiado largo para +-- el tipo character varying(3)" cuando un contribuyente tiene ≥2 regímenes +-- activos en su CSF, y los campos regimen_fiscal/codigo_postal/domicilio +-- quedan NULL. +-- +-- Idempotente: si ya es text (patito tenía parche manual), el ALTER es no-op +-- en términos de filas — Postgres lo resuelve como metadata change. + +ALTER TABLE contribuyentes ALTER COLUMN regimen_fiscal TYPE text; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 25, '025_contribuyentes_regimen_fiscal_text') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/026_normalize_cfdi_uuid_case.sql b/apps/api/src/migrations/tenant/026_normalize_cfdi_uuid_case.sql new file mode 100644 index 0000000..31a8800 --- /dev/null +++ b/apps/api/src/migrations/tenant/026_normalize_cfdi_uuid_case.sql @@ -0,0 +1,47 @@ +-- Normaliza el case de cfdis.uuid y elimina duplicados generados por el +-- mismatch case-sensitive entre los dos paths de sync SAT: +-- - source='sat' (XML parser): insertaba UUIDs como venían del XML (lowercase) +-- - source='sat-metadata' (CSV parser): insertaba UUIDs del CSV (UPPERCASE) +-- La constraint UNIQUE(uuid) de Postgres es case-sensitive → ambos convivían +-- como filas distintas, duplicando el CFDI en todas las métricas. +-- +-- Estrategia (idempotente — en tenants sin duplicados es no-op): +-- 1. Propagar status=Cancelado de metadata→sat si corresponde (guard de +-- data loss; verificado que en Patito no aplica a ninguna fila, pero la +-- cláusula queda como protección para futuros despachos). +-- 2. Borrar las filas 'sat-metadata' que tengan par 'sat' con el mismo UUID +-- (case-insensitive). Las filas sat-metadata sin par se conservan (son +-- legítimas: CFDIs cancelados sin XML). +-- 3. Normalizar todos los UUIDs restantes a lowercase. +-- El código de saveCfdis/saveMetadata también fue actualizado para (a) matchear +-- case-insensitive, (b) insertar siempre en lowercase. + +-- 1. Propagar Cancelado antes de borrar +UPDATE cfdis sat +SET status = 'Cancelado', + fecha_cancelacion = COALESCE(sat.fecha_cancelacion, meta.fecha_cancelacion), + actualizado_en = NOW() +FROM cfdis meta +WHERE LOWER(sat.uuid) = LOWER(meta.uuid) + AND sat.id != meta.id + AND sat.source = 'sat' + AND meta.source = 'sat-metadata' + AND meta.status = 'Cancelado' + AND sat.status != 'Cancelado'; + +-- 2. Borrar duplicados sat-metadata +DELETE FROM cfdis meta +WHERE meta.source = 'sat-metadata' + AND EXISTS ( + SELECT 1 FROM cfdis sat + WHERE LOWER(meta.uuid) = LOWER(sat.uuid) + AND sat.id != meta.id + AND sat.source = 'sat' + ); + +-- 3. Normalizar a lowercase +UPDATE cfdis SET uuid = LOWER(uuid) WHERE uuid IS NOT NULL AND uuid != LOWER(uuid); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 26, '026_normalize_cfdi_uuid_case') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/027_cfdi_uuid_unique_case_insensitive.sql b/apps/api/src/migrations/tenant/027_cfdi_uuid_unique_case_insensitive.sql new file mode 100644 index 0000000..0e3b33f --- /dev/null +++ b/apps/api/src/migrations/tenant/027_cfdi_uuid_unique_case_insensitive.sql @@ -0,0 +1,22 @@ +-- Reemplaza el UNIQUE (uuid) case-sensitive por un índice funcional +-- UNIQUE (LOWER(uuid)), como defensa en profundidad contra duplicados por +-- mismatch de case. El código fuente ya normaliza a lowercase en el insert +-- (saveCfdis y saveMetadata en sat.service.ts), pero este constraint previene +-- que cualquier insert manual o vía futuro path pueda reintroducir el bug. +-- +-- Prerequisito: migración 026 ya normalizó todos los UUIDs existentes a +-- lowercase y eliminó duplicados case-insensitive. Si no se aplicó antes, el +-- CREATE UNIQUE INDEX fallará con "could not create unique index" y habrá que +-- correr 026 primero. +-- +-- Idempotente: si el índice nuevo ya existe, IF NOT EXISTS lo salta. + +ALTER TABLE cfdis DROP CONSTRAINT IF EXISTS cfdis_uuid_key; + +CREATE UNIQUE INDEX IF NOT EXISTS cfdis_uuid_lower_key + ON cfdis (LOWER(uuid)) + WHERE uuid IS NOT NULL; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 27, '027_cfdi_uuid_unique_case_insensitive') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/028_documentos_extras.sql b/apps/api/src/migrations/tenant/028_documentos_extras.sql new file mode 100644 index 0000000..c93eb6d --- /dev/null +++ b/apps/api/src/migrations/tenant/028_documentos_extras.sql @@ -0,0 +1,26 @@ +-- Pestaña "Extras" en /documentos: PDFs libres (acuses SAT, contratos, poderes, +-- estados de cuenta, comprobantes) organizados por contribuyente con categoría +-- de texto libre. +CREATE TABLE IF NOT EXISTS documentos_extras ( + id serial PRIMARY KEY, + contribuyente_id uuid REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + nombre varchar(255) NOT NULL, + descripcion text, + categoria varchar(100), + pdf bytea NOT NULL, + pdf_filename varchar(255) NOT NULL, + subido_por varchar(255), + created_at timestamptz DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS ix_documentos_extras_contrib + ON documentos_extras(contribuyente_id, created_at DESC) + WHERE contribuyente_id IS NOT NULL; + +CREATE INDEX IF NOT EXISTS ix_documentos_extras_categoria + ON documentos_extras(categoria) + WHERE categoria IS NOT NULL; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 28, '028_documentos_extras') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/030_obligacion_periodos_declaracion_id.sql b/apps/api/src/migrations/tenant/030_obligacion_periodos_declaracion_id.sql new file mode 100644 index 0000000..54dfe64 --- /dev/null +++ b/apps/api/src/migrations/tenant/030_obligacion_periodos_declaracion_id.sql @@ -0,0 +1,17 @@ +-- #6 Trazabilidad declaración↔obligación: agrega FK a declaraciones_provisionales +-- en obligacion_periodos. ON DELETE SET NULL porque si la declaración se borra +-- el periodo puede seguir completado (el usuario puede haberlo cerrado sin +-- re-subir, o la completitud viene de otra fuente — "marcar manualmente" +-- via UI, etc.). La UI puede mostrar "via Declaración #123" cuando hay FK. + +ALTER TABLE obligacion_periodos + ADD COLUMN IF NOT EXISTS declaracion_id integer + REFERENCES declaraciones_provisionales(id) ON DELETE SET NULL; + +CREATE INDEX IF NOT EXISTS ix_obligacion_periodos_declaracion + ON obligacion_periodos(declaracion_id) + WHERE declaracion_id IS NOT NULL; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 30, '030_obligacion_periodos_declaracion_id') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/031_declaraciones_contribuyente_id.sql b/apps/api/src/migrations/tenant/031_declaraciones_contribuyente_id.sql new file mode 100644 index 0000000..de251ca --- /dev/null +++ b/apps/api/src/migrations/tenant/031_declaraciones_contribuyente_id.sql @@ -0,0 +1,27 @@ +-- Fix: las declaraciones provisionales no distinguían contribuyente. En un +-- despacho con N RFCs, la declaración IVA de Alexa aparecía también cuando +-- se seleccionaba a Carlos. Agregamos FK nullable para linkear, y las +-- existentes quedan con NULL (interpretadas como "tenant-wide / legacy"). +-- `ON DELETE SET NULL` para que borrar un contribuyente no tire declaraciones. + +ALTER TABLE declaraciones_provisionales + ADD COLUMN IF NOT EXISTS contribuyente_id uuid + REFERENCES contribuyentes(entidad_id) ON DELETE SET NULL; + +CREATE INDEX IF NOT EXISTS ix_declaraciones_contribuyente + ON declaraciones_provisionales(contribuyente_id, año DESC, mes DESC) + WHERE contribuyente_id IS NOT NULL; + +-- Reemplaza el UNIQUE (año, mes) WHERE tipo='normal' por uno que incluye +-- contribuyente: cada RFC debe poder tener su propia declaración normal +-- para el mismo mes. Postgres trata NULL != NULL en índices, así que +-- declaraciones legacy sin contribuyente siguen pudiendo coexistir entre +-- sí — no se auto-de-duplican, pero tampoco bloquean las nuevas. +DROP INDEX IF EXISTS uniq_declaracion_normal_mes; +CREATE UNIQUE INDEX IF NOT EXISTS uniq_declaracion_normal_mes_contrib + ON declaraciones_provisionales(año, mes, contribuyente_id) + WHERE tipo = 'normal'; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 31, '031_declaraciones_contribuyente_id') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/032_cfdis_relaciones.sql b/apps/api/src/migrations/tenant/032_cfdis_relaciones.sql new file mode 100644 index 0000000..7b269dc --- /dev/null +++ b/apps/api/src/migrations/tenant/032_cfdis_relaciones.sql @@ -0,0 +1,22 @@ +-- Agrega soporte para CfdiRelacionados del propio comprobante (CFDI 4.0). +-- El campo existente `uuid_relacionado` se sigue usando para DoctoRelacionado +-- del complemento de Pagos (tipo P). Estas dos columnas nuevas son para los +-- CfdiRelacionados a nivel raíz del comprobante (típico en tipo E — notas +-- de crédito relacionadas a facturas I, P, o a anticipos aplicados). +-- +-- `cfdi_tipo_relacion` — clave SAT de 2 chars (01 NC, 02 Sustitución, +-- 03 Devolución, 04 Sustitución CFDIs previos, 05 Traslados mercancía, +-- 06 Factura por traslado previo, 07 Aplicación de anticipo). +-- `cfdis_relacionados` — UUIDs pipe-separated del/los CfdiRelacionado. + +ALTER TABLE cfdis + ADD COLUMN IF NOT EXISTS cfdi_tipo_relacion VARCHAR(2), + ADD COLUMN IF NOT EXISTS cfdis_relacionados TEXT; + +CREATE INDEX IF NOT EXISTS ix_cfdis_tipo_relacion + ON cfdis(cfdi_tipo_relacion) + WHERE cfdi_tipo_relacion IS NOT NULL; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 32, '032_cfdis_relaciones') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/033_facturapi_orgs_lco_rejection.sql b/apps/api/src/migrations/tenant/033_facturapi_orgs_lco_rejection.sql new file mode 100644 index 0000000..0033bc1 --- /dev/null +++ b/apps/api/src/migrations/tenant/033_facturapi_orgs_lco_rejection.sql @@ -0,0 +1,11 @@ +-- Marca el timestamp del último rechazo SAT que sugiere que el CSD +-- aún no está propagado en la Lista de Contribuyentes Obligados (LCO). +-- La propagación tarda 24-72h; el frontend muestra un banner mientras +-- esta marca esté dentro de las últimas 24h. + +ALTER TABLE facturapi_orgs + ADD COLUMN IF NOT EXISTS last_lco_rejection_at timestamptz; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 33, '033_facturapi_orgs_lco_rejection') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/034_contribuyentes_email_preferences.sql b/apps/api/src/migrations/tenant/034_contribuyentes_email_preferences.sql new file mode 100644 index 0000000..2ab8052 --- /dev/null +++ b/apps/api/src/migrations/tenant/034_contribuyentes_email_preferences.sql @@ -0,0 +1,12 @@ +-- Preferencias de notificación por correo, por contribuyente. +-- Default: objeto vacío → el código interpreta "todo activado" como +-- comportamiento previo. Cuando el user desactiva un tipo, se guarda +-- `{ "": false }`. Tipos soportados (informativos): +-- weekly_update, fiel_notification, documento_subido, subscription_expiring + +ALTER TABLE contribuyentes + ADD COLUMN IF NOT EXISTS email_preferences jsonb DEFAULT '{}'::jsonb; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 34, '034_contribuyentes_email_preferences') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/035_tareas.sql b/apps/api/src/migrations/tenant/035_tareas.sql new file mode 100644 index 0000000..c10ef05 --- /dev/null +++ b/apps/api/src/migrations/tenant/035_tareas.sql @@ -0,0 +1,46 @@ +-- Tareas operativas del despacho por contribuyente. Recurrentes (semanal a +-- anual). Materialización lazy en tarea_periodos cuando el frontend lee. +-- Solo del presente en adelante. + +CREATE TABLE IF NOT EXISTS tareas_catalogo ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + nombre text NOT NULL, + descripcion text, + recurrencia varchar(15) NOT NULL CHECK (recurrencia IN + ('semanal','quincenal','mensual','bimestral','trimestral','semestral','anual')), + -- Para semanal/quincenal: día de la semana (1=lunes, 7=domingo) + dia_semana int CHECK (dia_semana BETWEEN 1 AND 7), + -- Para mensual y mayores: día del mes (1-31). Si > último día del mes, + -- se materializa al último día (ej. 31 en febrero → 28/29). + dia_mes int CHECK (dia_mes BETWEEN 1 AND 31), + solo_supervisor_completa boolean DEFAULT false, + es_default boolean DEFAULT false, + active boolean DEFAULT true, + orden int DEFAULT 0, + created_at timestamptz DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS ix_tareas_catalogo_contrib ON tareas_catalogo(contribuyente_id, active); + +CREATE TABLE IF NOT EXISTS tarea_periodos ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + tarea_id uuid NOT NULL REFERENCES tareas_catalogo(id) ON DELETE CASCADE, + -- '2025-W12' semanal/quincenal, '2025-01' mensual, '2025-B1' bimestral, + -- '2025-Q1' trimestral, '2025-S1' semestral, '2025' anual. + periodo varchar(10) NOT NULL, + fecha_limite date NOT NULL, + completada boolean DEFAULT false, + completada_at timestamptz, + completada_por uuid, + notas text, + created_at timestamptz DEFAULT now(), + UNIQUE (tarea_id, periodo) +); + +CREATE INDEX IF NOT EXISTS ix_tarea_periodos_fecha ON tarea_periodos(fecha_limite); +CREATE INDEX IF NOT EXISTS ix_tarea_periodos_completada ON tarea_periodos(completada); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 35, '035_tareas') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/036_papeleria_trabajo.sql b/apps/api/src/migrations/tenant/036_papeleria_trabajo.sql new file mode 100644 index 0000000..25fd635 --- /dev/null +++ b/apps/api/src/migrations/tenant/036_papeleria_trabajo.sql @@ -0,0 +1,38 @@ +-- Papelería de trabajo: archivos del despacho por contribuyente, organizados +-- por mes/año y con flujo opcional de aprobación. Formatos permitidos +-- (validados en backend): pdf, word (doc/docx), excel (xls/xlsx). Máx 5 MB +-- por archivo (validado en backend). NO accesible para usuarios rol cliente. + +CREATE TABLE IF NOT EXISTS papeleria_trabajo ( + id serial PRIMARY KEY, + contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE, + nombre varchar(255) NOT NULL, -- "Reporte de cuentas Q1" + descripcion text, + archivo bytea NOT NULL, + archivo_filename varchar(255) NOT NULL, -- "reporte.pdf" + archivo_mime varchar(100) NOT NULL, + archivo_size int NOT NULL, + -- periodo (mes + año) + anio int NOT NULL CHECK (anio BETWEEN 2000 AND 2100), + mes int NOT NULL CHECK (mes BETWEEN 1 AND 12), + -- flujo de aprobación + requiere_aprobacion boolean NOT NULL DEFAULT false, + estado varchar(20) CHECK (estado IS NULL OR estado IN ('pendiente','aprobado','rechazado')), + aprobado_por uuid, + aprobado_at timestamptz, + comentario_rechazo text, + subido_por uuid NOT NULL, + created_at timestamptz DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS ix_papeleria_contrib + ON papeleria_trabajo(contribuyente_id, created_at DESC); +CREATE INDEX IF NOT EXISTS ix_papeleria_periodo + ON papeleria_trabajo(contribuyente_id, anio, mes); +CREATE INDEX IF NOT EXISTS ix_papeleria_estado + ON papeleria_trabajo(estado) + WHERE estado IS NOT NULL; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 36, '036_papeleria_trabajo') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/037_activos_fijos_baja.sql b/apps/api/src/migrations/tenant/037_activos_fijos_baja.sql new file mode 100644 index 0000000..3912dc5 --- /dev/null +++ b/apps/api/src/migrations/tenant/037_activos_fijos_baja.sql @@ -0,0 +1,22 @@ +-- Tracking de activos fijos dados de baja (vendidos / desechados / otro). +-- Solo aplica a CFDIs tipo I con uso_cfdi I01-I08 cuyo receptor es el +-- contribuyente. Una fila por CFDI dado de baja; revertir = DELETE. +-- La pestaña "Activos Fijos" en /impuestos consulta esta tabla para +-- detener el cómputo de deducción mensual a partir de `fecha_baja`. + +CREATE TABLE IF NOT EXISTS activos_fijos_baja ( + id serial PRIMARY KEY, + cfdi_id int NOT NULL REFERENCES cfdis(id) ON DELETE CASCADE, + fecha_baja date NOT NULL, + motivo varchar(20) NOT NULL CHECK (motivo IN ('venta','desecho','otro')), + comentario text, + dado_de_baja_por uuid NOT NULL, + created_at timestamptz DEFAULT now(), + UNIQUE (cfdi_id) +); + +CREATE INDEX IF NOT EXISTS ix_activos_fijos_baja_cfdi ON activos_fijos_baja(cfdi_id); + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 37, '037_activos_fijos_baja') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/038_activos_fijos_usos_excluidos.sql b/apps/api/src/migrations/tenant/038_activos_fijos_usos_excluidos.sql new file mode 100644 index 0000000..4805353 --- /dev/null +++ b/apps/api/src/migrations/tenant/038_activos_fijos_usos_excluidos.sql @@ -0,0 +1,11 @@ +-- Permite al contador descartar conceptos de uso CFDI (ej. I06, I07) que +-- en su contribuyente no representan adquisiciones de activos fijos sino +-- gastos regulares (ej. servicio telefónico mensual). Default: lista vacía +-- (todos los usos I01-I08 se consideran activos fijos como hasta ahora). + +ALTER TABLE contribuyentes + ADD COLUMN IF NOT EXISTS activos_fijos_usos_excluidos jsonb DEFAULT '[]'::jsonb; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 38, '038_activos_fijos_usos_excluidos') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/039_alertas_notificadas.sql b/apps/api/src/migrations/tenant/039_alertas_notificadas.sql new file mode 100644 index 0000000..3746623 --- /dev/null +++ b/apps/api/src/migrations/tenant/039_alertas_notificadas.sql @@ -0,0 +1,22 @@ +-- Tracking de alertas automáticas que ya fueron notificadas por email. +-- Mecanismo idempotente: una sola email por (alerta_id, contribuyente_id). +-- Si la alerta deja de devolverse por `generarAlertasAutomaticas`, se marca +-- `resuelta_at`. Si vuelve a aparecer (NULL en resuelta_at), no se re-notifica +-- — política conservadora de Option B (una sola notificación por evento). + +CREATE TABLE IF NOT EXISTS alertas_notificadas ( + id BIGSERIAL PRIMARY KEY, + alerta_id TEXT NOT NULL, + contribuyente_id UUID, + notified_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + resuelta_at TIMESTAMPTZ +); + +-- UNIQUE compuesto con COALESCE para que NULL en contribuyente_id no rompa +-- la dedup (alertas tenant-level vs contribuyente-específicas comparten tabla). +CREATE UNIQUE INDEX IF NOT EXISTS uniq_alertas_notif + ON alertas_notificadas (alerta_id, COALESCE(contribuyente_id::text, '')); + +-- Índice para queries del cron que filtra por contribuyente. +CREATE INDEX IF NOT EXISTS idx_alertas_notif_contribuyente + ON alertas_notificadas (contribuyente_id) WHERE contribuyente_id IS NOT NULL; diff --git a/apps/api/src/migrations/tenant/040_recordatorios_email_notif.sql b/apps/api/src/migrations/tenant/040_recordatorios_email_notif.sql new file mode 100644 index 0000000..1a777b0 --- /dev/null +++ b/apps/api/src/migrations/tenant/040_recordatorios_email_notif.sql @@ -0,0 +1,12 @@ +-- Tracking de notificaciones email enviadas por recordatorio en cada +-- ventana de proximidad (3 días, 1 día, mismo día). Cada columna se llena +-- una sola vez cuando el cron envía el email correspondiente. +-- +-- Si el usuario edita `fecha_limite` después de haber enviado un email, +-- las columnas previas siguen marcadas — el cron no re-notificará para +-- ventanas ya enviadas. Decisión MVP: simple y predecible. + +ALTER TABLE recordatorios + ADD COLUMN IF NOT EXISTS email_3d_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS email_1d_at TIMESTAMPTZ, + ADD COLUMN IF NOT EXISTS email_0d_at TIMESTAMPTZ; diff --git a/apps/api/src/migrations/tenant/041_facturapi_orgs_api_key_enc.sql b/apps/api/src/migrations/tenant/041_facturapi_orgs_api_key_enc.sql new file mode 100644 index 0000000..803e99e --- /dev/null +++ b/apps/api/src/migrations/tenant/041_facturapi_orgs_api_key_enc.sql @@ -0,0 +1,13 @@ +-- Live Secret Key por organización Facturapi (modo Live multi-RFC). +-- Cada organización Facturapi (1:1 con contribuyente del despacho) tiene su +-- propia sk_live_xxx generada vía PUT /v2/organizations/{id}/apikeys/live. +-- La key se cifra con AES-256-GCM (misma derivación que FIEL_ENCRYPTION_KEY) +-- y se guarda con IV + auth tag por componente, igual que las credenciales FIEL. +ALTER TABLE facturapi_orgs + ADD COLUMN IF NOT EXISTS api_key_enc bytea, + ADD COLUMN IF NOT EXISTS api_key_iv bytea, + ADD COLUMN IF NOT EXISTS api_key_tag bytea; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 41, '041_facturapi_orgs_api_key_enc') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/042_metricas_ncs.sql b/apps/api/src/migrations/tenant/042_metricas_ncs.sql new file mode 100644 index 0000000..92e7206 --- /dev/null +++ b/apps/api/src/migrations/tenant/042_metricas_ncs.sql @@ -0,0 +1,15 @@ +-- NCs Emitidas y NCs Recibidas — surface metrics nuevas en /impuestos > ISR. +-- Persistidas en metricas_mensuales para acceso vía cache (mismo patrón que +-- ingresos_cobrados/egresos_pagados) y disponibles para queries directas / +-- reportes BI sin tener que recomputar desde raw CFDIs. +-- +-- Defecto 0 — los registros existentes se exponen con valor 0 hasta que el +-- cron de invalidaciones los recompute. El cache total se invalida al deploy +-- vía scripts/refresh-metricas-cache.ts. +ALTER TABLE metricas_mensuales + ADD COLUMN IF NOT EXISTS ncs_emitidas numeric(18,2) DEFAULT 0, + ADD COLUMN IF NOT EXISTS ncs_recibidas numeric(18,2) DEFAULT 0; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 42, '042_metricas_ncs') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/migrations/tenant/043_metricas_no_deducibles.sql b/apps/api/src/migrations/tenant/043_metricas_no_deducibles.sql new file mode 100644 index 0000000..5ebfdd9 --- /dev/null +++ b/apps/api/src/migrations/tenant/043_metricas_no_deducibles.sql @@ -0,0 +1,13 @@ +-- Gastos no deducibles por Art. 27 fracción III LISR — facturas recibidas +-- pagadas en efectivo (forma_pago='01') con monto > $2,000. Persistido en +-- metricas_mensuales para acceso vía cache + queries directas / reportes BI. +-- +-- Defecto 0 — los registros existentes se exponen con 0 hasta que el cron +-- de invalidaciones los recompute. El cache total se invalida al deploy +-- vía scripts/refresh-metricas-cache.ts. +ALTER TABLE metricas_mensuales + ADD COLUMN IF NOT EXISTS gastos_no_deducibles_efectivo numeric(18,2) DEFAULT 0; + +INSERT INTO tenant_migrations (scope, version, name) +VALUES ('vertical-contable', 43, '043_metricas_no_deducibles') +ON CONFLICT (scope, version) DO NOTHING; diff --git a/apps/api/src/routes/admin-addons.routes.ts b/apps/api/src/routes/admin-addons.routes.ts new file mode 100644 index 0000000..d87857f --- /dev/null +++ b/apps/api/src/routes/admin-addons.routes.ts @@ -0,0 +1,11 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import * as ctrl from '../controllers/admin-addons.controller.js'; + +const router: IRouter = Router(); +router.use(authenticate); + +router.get('/catalogo', ctrl.listCatalogo); +router.put('/catalogo/:id', ctrl.updateCatalogoItem); + +export default router; diff --git a/apps/api/src/routes/admin-clientes.routes.ts b/apps/api/src/routes/admin-clientes.routes.ts new file mode 100644 index 0000000..7ace09d --- /dev/null +++ b/apps/api/src/routes/admin-clientes.routes.ts @@ -0,0 +1,11 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import * as ctrl from '../controllers/admin-clientes.controller.js'; + +const router: IRouter = Router(); +router.use(authenticate); + +router.get('/stats', ctrl.getStats); +router.get('/:tenantId/usuarios', ctrl.listUsuarios); + +export default router; diff --git a/apps/api/src/routes/admin-dashboard.routes.ts b/apps/api/src/routes/admin-dashboard.routes.ts new file mode 100644 index 0000000..594fa2c --- /dev/null +++ b/apps/api/src/routes/admin-dashboard.routes.ts @@ -0,0 +1,12 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import * as ctrl from '../controllers/admin-dashboard.controller.js'; + +const router: IRouter = Router(); +router.use(authenticate); + +router.get('/metrics', ctrl.getMetrics); +router.get('/despachos', ctrl.listDespachos); +router.get('/activity', ctrl.getActivity); + +export default router; diff --git a/apps/api/src/routes/admin-impersonate.routes.ts b/apps/api/src/routes/admin-impersonate.routes.ts new file mode 100644 index 0000000..4580c36 --- /dev/null +++ b/apps/api/src/routes/admin-impersonate.routes.ts @@ -0,0 +1,11 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import * as ctrl from '../controllers/admin-impersonate.controller.js'; + +const router: IRouter = Router(); +router.use(authenticate); + +router.post('/', ctrl.startImpersonation); +router.post('/stop', ctrl.stopImpersonation); + +export default router; diff --git a/apps/api/src/routes/alertas.routes.ts b/apps/api/src/routes/alertas.routes.ts new file mode 100644 index 0000000..fb7cc7a --- /dev/null +++ b/apps/api/src/routes/alertas.routes.ts @@ -0,0 +1,34 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as alertasController from '../controllers/alertas.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/', alertasController.getAlertas); +router.get('/automaticas', alertasController.getAlertasAutomaticas); +router.get('/manuales', alertasController.getManualesPendientes); +router.patch('/manuales/:id/resolver', alertasController.resolverAlertaManual); +router.get('/drilldown/lista-negra-clientes', alertasController.getListaNegraClientes); +router.get('/drilldown/lista-negra-proveedores', alertasController.getListaNegraProveedores); +router.get('/drilldown/concentracion-clientes', alertasController.getConcentracionClientes); +router.get('/drilldown/concentracion-proveedores', alertasController.getConcentracionProveedores); +router.get('/drilldown/discrepancia-regimen', alertasController.getDiscrepanciaRegimen); +router.get('/drilldown/cancelaciones', alertasController.getCancelados); +router.get('/drilldown/cancelaciones-periodo-anterior', alertasController.getCancelacionesPeriodoAnterior); +router.get('/drilldown/efectivo', alertasController.getEfectivo); +router.get('/drilldown/tipo-relacion-sospechosa', alertasController.getTipoRelacionSospechosa); +router.post('/descartar', alertasController.descartarCfdis); +router.delete('/descartar', alertasController.restaurarDescartados); +router.get('/descartados', alertasController.getDescartados); +router.get('/stats', alertasController.getStats); +router.post('/mark-all-read', alertasController.markAllAsRead); +router.get('/:id', alertasController.getAlerta); +router.post('/', alertasController.createAlerta); +router.patch('/:id', alertasController.updateAlerta); +router.delete('/:id', alertasController.deleteAlerta); + +export { router as alertasRoutes }; diff --git a/apps/api/src/routes/audit-log.routes.ts b/apps/api/src/routes/audit-log.routes.ts new file mode 100644 index 0000000..a5996be --- /dev/null +++ b/apps/api/src/routes/audit-log.routes.ts @@ -0,0 +1,13 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import * as auditLogController from '../controllers/audit-log.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(authorize('owner', 'cfo')); + +// Solo admin global (verificado dentro del controller) +router.get('/', auditLogController.listAuditLog); + +export { router as auditLogRoutes }; diff --git a/apps/api/src/routes/auth.routes.ts b/apps/api/src/routes/auth.routes.ts new file mode 100644 index 0000000..6aac029 --- /dev/null +++ b/apps/api/src/routes/auth.routes.ts @@ -0,0 +1,69 @@ +import { Router, type IRouter } from 'express'; +import rateLimit from 'express-rate-limit'; +import * as authController from '../controllers/auth.controller.js'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { strictLimit } from '../middlewares/rate-limit.middleware.js'; + +const router: IRouter = Router(); + +// Rate limiting: 10 login attempts per 15 minutes per IP +const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 10, + message: { message: 'Demasiados intentos de login. Intenta de nuevo en 15 minutos.' }, + standardHeaders: true, + legacyHeaders: false, +}); + +// Rate limiting: 3 registrations per hour per IP +const registerLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, + max: 3, + message: { message: 'Demasiados registros. Intenta de nuevo en 1 hora.' }, + standardHeaders: true, + legacyHeaders: false, +}); + +// Rate limiting: 20 refresh attempts per 15 minutes per IP +const refreshLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 20, + message: { message: 'Demasiadas solicitudes. Intenta de nuevo más tarde.' }, + standardHeaders: true, + legacyHeaders: false, +}); + +// Rate limiting: 3 password reset requests per hour per IP — evita spam + enumeration +const passwordResetRequestLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, + max: 3, + message: { message: 'Demasiadas solicitudes de recuperación. Intenta de nuevo en 1 hora.' }, + standardHeaders: true, + legacyHeaders: false, +}); + +// Rate limiting: 10 confirm attempts per hour per IP — prevenir brute-force del token +const passwordResetConfirmLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, + max: 10, + message: { message: 'Demasiados intentos. Intenta de nuevo más tarde.' }, + standardHeaders: true, + legacyHeaders: false, +}); + +router.post('/register', registerLimiter, authController.register); +router.post('/login', loginLimiter, authController.login); +router.post('/refresh', refreshLimiter, authController.refresh); +router.post('/logout', authenticate, authController.logout); +router.get('/me', authenticate, authController.me); +router.post('/password-reset/request', passwordResetRequestLimiter, authController.requestPasswordReset); +router.post('/password-reset/confirm', passwordResetConfirmLimiter, authController.confirmPasswordReset); +// 10/hora — prevenir brute-force del currentPassword +router.post('/password-change', authenticate, strictLimit, authController.changePassword); +router.post('/logout-all', authenticate, authController.logoutAll); +router.post('/switch-tenant', authenticate, authController.switchTenant); +// Auto-dismiss del onboarding (lo llama el frontend cuando el user completa +// los pasos requeridos). Idempotente — múltiples llamadas no rompen. +router.post('/onboarding/dismiss', authenticate, authController.dismissOnboarding); + +export { router as authRoutes }; diff --git a/apps/api/src/routes/bancos.routes.ts b/apps/api/src/routes/bancos.routes.ts new file mode 100644 index 0000000..1b751ac --- /dev/null +++ b/apps/api/src/routes/bancos.routes.ts @@ -0,0 +1,16 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as bancosController from '../controllers/bancos.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/', bancosController.getBancos); +router.post('/', bancosController.createBanco); +router.put('/:id', bancosController.updateBanco); +router.delete('/:id', bancosController.deleteBanco); + +export { router as bancosRoutes }; diff --git a/apps/api/src/routes/calendario.routes.ts b/apps/api/src/routes/calendario.routes.ts new file mode 100644 index 0000000..7b69c83 --- /dev/null +++ b/apps/api/src/routes/calendario.routes.ts @@ -0,0 +1,23 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as calendarioController from '../controllers/calendario.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +// GET /api/calendario/generados — eventos fiscales + recordatorios custom +router.get('/generados', calendarioController.getEventosGenerados); + +// POST /api/calendario — crear recordatorio custom +router.post('/', calendarioController.createRecordatorio); + +// PATCH /api/calendario/:id — editar recordatorio custom +router.patch('/:id', calendarioController.updateRecordatorio); + +// DELETE /api/calendario/:id — eliminar recordatorio custom +router.delete('/:id', calendarioController.deleteRecordatorio); + +export { router as calendarioRoutes }; diff --git a/apps/api/src/routes/cartera.routes.ts b/apps/api/src/routes/cartera.routes.ts new file mode 100644 index 0000000..308415f --- /dev/null +++ b/apps/api/src/routes/cartera.routes.ts @@ -0,0 +1,32 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/cartera.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +// Static routes first +router.get('/supervisores', authorize('owner'), ctrl.getSupervisores); + +// Read: owner + supervisor + auxiliar +router.get('/', authorize('owner', 'supervisor', 'auxiliar'), ctrl.list); +router.get('/:id', authorize('owner', 'supervisor', 'auxiliar'), ctrl.getById); +router.get('/:id/subcarteras', authorize('owner', 'supervisor', 'auxiliar'), ctrl.listSubcarteras); +router.get('/:id/entidades', authorize('owner', 'supervisor', 'auxiliar'), ctrl.getEntidades); +router.get('/:id/auxiliares', authorize('owner', 'supervisor', 'auxiliar'), ctrl.getAuxiliares); +router.get('/:supervisorId/auxiliares-disponibles', authorize('owner', 'supervisor'), ctrl.getAuxiliaresDelSupervisor); + +// Write: owner + supervisor (with permission checks in controller) +router.post('/', authorize('owner', 'supervisor'), ctrl.create); +router.put('/:id', authorize('owner', 'supervisor'), ctrl.update); +router.delete('/:id', authorize('owner', 'supervisor'), ctrl.remove); +router.post('/:id/subcarteras', authorize('owner', 'supervisor'), ctrl.createSubcartera); +router.post('/:id/entidades', authorize('owner', 'supervisor'), ctrl.addEntidad); +router.delete('/:id/entidades/:entidadId', authorize('owner', 'supervisor'), ctrl.removeEntidad); +router.post('/:id/auxiliares', authorize('owner', 'supervisor'), ctrl.addAuxiliar); +router.delete('/:id/auxiliares/:auxiliarUserId', authorize('owner', 'supervisor'), ctrl.removeAuxiliar); + +export default router; diff --git a/apps/api/src/routes/catalogos.routes.ts b/apps/api/src/routes/catalogos.routes.ts new file mode 100644 index 0000000..9b63eda --- /dev/null +++ b/apps/api/src/routes/catalogos.routes.ts @@ -0,0 +1,21 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { relaxedLimit } from '../middlewares/rate-limit.middleware.js'; +import * as catalogosController from '../controllers/catalogos.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(relaxedLimit); + +router.get('/forma-pago', catalogosController.getFormasPago); +router.get('/metodo-pago', catalogosController.getMetodosPago); +router.get('/uso-cfdi', catalogosController.getUsosCfdi); +router.get('/moneda', catalogosController.getMonedas); +router.get('/clave-unidad', catalogosController.getClavesUnidad); +router.get('/clave-prod-serv', catalogosController.searchClaveProdServ); +router.get('/objeto-imp', catalogosController.getObjetosImp); +router.get('/tipo-relacion', catalogosController.getTiposRelacion); +router.get('/exportacion', catalogosController.getExportaciones); + +export { router as catalogosRoutes }; diff --git a/apps/api/src/routes/cfdi.routes.ts b/apps/api/src/routes/cfdi.routes.ts new file mode 100644 index 0000000..75ccd62 --- /dev/null +++ b/apps/api/src/routes/cfdi.routes.ts @@ -0,0 +1,31 @@ +import { Router, type IRouter } from 'express'; +import express from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { checkPlanLimits, checkCfdiLimit } from '../middlewares/plan-limits.middleware.js'; +import { strictLimit } from '../middlewares/rate-limit.middleware.js'; +import * as cfdiController from '../controllers/cfdi.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); +router.use(checkPlanLimits); + +router.get('/', cfdiController.getCfdis); +router.get('/resumen', cfdiController.getResumen); +router.get('/emisores', cfdiController.getEmisores); +router.get('/receptores', cfdiController.getReceptores); +router.get('/drill-down', cfdiController.drillDown); +// Listado de conceptos cross-CFDI (pestaña Conceptos en /cfdi). +// Debe registrarse antes que /:id para que Express no lo trate como id. +router.get('/conceptos', cfdiController.listConceptos); +router.get('/:id', cfdiController.getCfdiById); +router.get('/:id/conceptos', cfdiController.getConceptos); +router.get('/:id/xml', cfdiController.getXml); +router.post('/', checkCfdiLimit, cfdiController.createCfdi); +// Bulk upload: 10/hora — procesa hasta 50MB, pesado en parseo + inserts +router.post('/bulk', strictLimit, express.json({ limit: '50mb' }), checkCfdiLimit, cfdiController.createManyCfdis); +router.delete('/:id', cfdiController.deleteCfdi); + +export { router as cfdiRoutes }; diff --git a/apps/api/src/routes/conciliacion.routes.ts b/apps/api/src/routes/conciliacion.routes.ts new file mode 100644 index 0000000..1ab1d4c --- /dev/null +++ b/apps/api/src/routes/conciliacion.routes.ts @@ -0,0 +1,17 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { requireFeature } from '../middlewares/feature-gate.middleware.js'; +import * as conciliacionController from '../controllers/conciliacion.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); +router.use(requireFeature('conciliacion')); + +router.get('/', conciliacionController.getCfdis); +router.post('/', conciliacionController.conciliar); +router.delete('/:id', conciliacionController.desconciliar); + +export { router as conciliacionRoutes }; diff --git a/apps/api/src/routes/connector.routes.ts b/apps/api/src/routes/connector.routes.ts new file mode 100644 index 0000000..ce041ce --- /dev/null +++ b/apps/api/src/routes/connector.routes.ts @@ -0,0 +1,15 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/connector.controller.js'; + +const router: IRouter = Router(); + +// Public endpoint — called by connector Docker container (no user JWT, uses HORUX_TOKEN) +router.post('/heartbeat', ctrl.heartbeat); + +// Authenticated endpoints — for tenant owners managing their connector +router.get('/status', authenticate, tenantMiddleware, ctrl.status); +router.post('/provision', authenticate, tenantMiddleware, authorize('owner', 'cfo'), ctrl.provision); + +export default router; diff --git a/apps/api/src/routes/contribuyente.routes.ts b/apps/api/src/routes/contribuyente.routes.ts new file mode 100644 index 0000000..63d012a --- /dev/null +++ b/apps/api/src/routes/contribuyente.routes.ts @@ -0,0 +1,47 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/contribuyente.controller.js'; +import * as configCtrl from '../controllers/contribuyente-config.controller.js'; +import * as obligacionesCtrl from '../controllers/obligaciones.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +// === Static routes FIRST (before /:id to avoid route conflict) === +router.get('/', ctrl.list); +router.post('/', authorize('owner', 'cfo'), ctrl.create); +router.post('/backfill', authorize('owner'), ctrl.backfill); +router.get('/catalogo-obligaciones', obligacionesCtrl.getCatalogo); + +// === Dynamic routes with :id === +router.get('/:id', ctrl.getById); +router.put('/:id', authorize('owner', 'cfo'), ctrl.update); +router.delete('/:id', authorize('owner'), ctrl.deactivate); +router.post('/:id/cliente-acceso', authorize('owner', 'supervisor'), ctrl.addClienteAcceso); + +// FIEL per contribuyente +router.post('/:id/fiel', authorize('owner', 'cfo'), configCtrl.uploadFiel); +router.get('/:id/fiel/status', configCtrl.fielStatus); +router.delete('/:id/fiel', authorize('owner', 'cfo'), configCtrl.deleteFiel); + +// Facturapi per contribuyente +router.post('/:id/facturapi/org', authorize('owner', 'cfo'), configCtrl.createOrg); +router.get('/:id/facturapi/status', configCtrl.orgStatus); +router.post('/:id/facturapi/csd', authorize('owner', 'cfo'), configCtrl.uploadCsd); + +// Obligaciones fiscales per contribuyente +router.get('/:id/obligaciones/periodo', obligacionesCtrl.getObligacionesPorPeriodo); +router.get('/:id/obligaciones', obligacionesCtrl.getObligaciones); +router.post('/:id/obligaciones/init', authorize('owner', 'cfo'), obligacionesCtrl.initRecomendaciones); +router.post('/:id/obligaciones', authorize('owner', 'cfo'), obligacionesCtrl.addObligacion); +router.delete('/:id/obligaciones/:obligacionId', authorize('owner', 'cfo'), obligacionesCtrl.removeObligacion); +router.post('/:id/obligaciones/:obligacionId/restore', authorize('owner', 'cfo'), obligacionesCtrl.restoreObligacion); +router.post('/:id/obligaciones/:obligacionId/complete', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.completeObligacion); +router.post('/:id/obligaciones/:obligacionId/uncomplete', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.uncompleteObligacion); +router.post('/:id/obligaciones/:obligacionId/complete-periodo', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.completePeriodo); +router.post('/:id/obligaciones/:obligacionId/uncomplete-periodo', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.uncompletePeriodo); + +export default router; diff --git a/apps/api/src/routes/dashboard.routes.ts b/apps/api/src/routes/dashboard.routes.ts new file mode 100644 index 0000000..0e89041 --- /dev/null +++ b/apps/api/src/routes/dashboard.routes.ts @@ -0,0 +1,20 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { checkPlanLimits } from '../middlewares/plan-limits.middleware.js'; +import { normalLimit } from '../middlewares/rate-limit.middleware.js'; +import * as dashboardController from '../controllers/dashboard.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(normalLimit); +router.use(tenantMiddleware); +router.use(checkPlanLimits); + +router.get('/kpis', dashboardController.getKpis); +router.get('/ingresos-egresos', dashboardController.getIngresosEgresos); +router.get('/regimenes-periodo', dashboardController.getRegimenesDelPeriodo); +router.get('/alertas', dashboardController.getAlertas); + +export { router as dashboardRoutes }; diff --git a/apps/api/src/routes/despacho-audit.routes.ts b/apps/api/src/routes/despacho-audit.routes.ts new file mode 100644 index 0000000..b61923e --- /dev/null +++ b/apps/api/src/routes/despacho-audit.routes.ts @@ -0,0 +1,12 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/despacho-audit.controller.js'; + +const router: IRouter = Router(); +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/', ctrl.getDespachoAuditLog); + +export default router; diff --git a/apps/api/src/routes/despacho-stats.routes.ts b/apps/api/src/routes/despacho-stats.routes.ts new file mode 100644 index 0000000..d9bc0bd --- /dev/null +++ b/apps/api/src/routes/despacho-stats.routes.ts @@ -0,0 +1,15 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/despacho-stats.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/contribuyentes-stats', ctrl.getContribuyentesStats); +router.get('/mis-asignados', ctrl.getMisAsignados); +router.get('/equipo-stats', ctrl.getEquipoStats); + +export { router as despachoStatsRoutes }; diff --git a/apps/api/src/routes/despacho.routes.ts b/apps/api/src/routes/despacho.routes.ts new file mode 100644 index 0000000..92338d6 --- /dev/null +++ b/apps/api/src/routes/despacho.routes.ts @@ -0,0 +1,17 @@ +import { Router, type IRouter } from 'express'; +import rateLimit from 'express-rate-limit'; +import { signup, getMyPlan } from '../controllers/despacho.controller.js'; +import { authenticate } from '../middlewares/auth.middleware.js'; + +const router: IRouter = Router(); + +const signupLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, + max: 5, + message: { message: 'Demasiados intentos de registro. Intenta en una hora.' }, +}); + +router.post('/signup', signupLimiter, signup); +router.get('/me/plan', authenticate, getMyPlan); + +export default router; diff --git a/apps/api/src/routes/documentos.routes.ts b/apps/api/src/routes/documentos.routes.ts new file mode 100644 index 0000000..d264bcc --- /dev/null +++ b/apps/api/src/routes/documentos.routes.ts @@ -0,0 +1,38 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { requireFeature } from '../middlewares/feature-gate.middleware.js'; +import { veryStrictLimit } from '../middlewares/rate-limit.middleware.js'; +import * as documentosController from '../controllers/documentos.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); +router.use(requireFeature('documentos')); + +router.get('/opiniones', documentosController.listarOpiniones); +router.get('/opiniones/:id/pdf', documentosController.descargarPdf); +// 2/día — Playwright headless contra portal SAT es caro +router.post('/opiniones/consultar', veryStrictLimit, authorize('owner', 'cfo'), documentosController.consultarManual); + +// Constancia de Situación Fiscal +router.get('/constancias', documentosController.listarConstancias); +router.get('/constancias/:id/pdf', documentosController.descargarConstanciaPdf); +router.post('/constancias/consultar', veryStrictLimit, authorize('owner', 'cfo'), documentosController.consultarConstanciaManual); + +// Declaraciones provisionales +router.get('/declaraciones', documentosController.listarDeclaraciones); +router.post('/declaraciones', documentosController.crearDeclaracion); +router.post('/declaraciones/:id/comprobante-pago', documentosController.subirComprobantePago); +router.get('/declaraciones/:id/pdf/:variant', documentosController.descargarDeclaracionPdf); +router.delete('/declaraciones/:id', documentosController.eliminarDeclaracion); + +// Extras (PDFs libres) +router.get('/extras', documentosController.listarExtras); +router.get('/extras/categorias', documentosController.listarCategoriasExtras); +router.post('/extras', documentosController.crearExtra); +router.get('/extras/:id/pdf', documentosController.descargarExtraPdf); +router.delete('/extras/:id', documentosController.eliminarExtra); + +export { router as documentosRoutes }; diff --git a/apps/api/src/routes/export.routes.ts b/apps/api/src/routes/export.routes.ts new file mode 100644 index 0000000..ed5eba8 --- /dev/null +++ b/apps/api/src/routes/export.routes.ts @@ -0,0 +1,14 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as exportController from '../controllers/export.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/cfdis', exportController.exportCfdis); +router.get('/reporte', exportController.exportReporte); + +export { router as exportRoutes }; diff --git a/apps/api/src/routes/facturacion.routes.ts b/apps/api/src/routes/facturacion.routes.ts new file mode 100644 index 0000000..e5c2b6f --- /dev/null +++ b/apps/api/src/routes/facturacion.routes.ts @@ -0,0 +1,64 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { strictLimit } from '../middlewares/rate-limit.middleware.js'; +import * as facturacionController from '../controllers/facturacion.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +// Organización Facturapi +router.get('/org/status', facturacionController.getOrgStatus); +router.post('/org', authorize('owner', 'cfo'), facturacionController.createOrg); + +// CSD +router.post('/csd', authorize('owner', 'cfo'), facturacionController.uploadCsd); + +// Estado LCO — banner "CSD en proceso de validación" si hubo rechazo del SAT en últimas 24h +router.get('/lco-status', facturacionController.getLcoStatus); + +// Emisión y cancelación (admin + contador). 10/hora — cada emisión quema timbre + side-effect en Facturapi/SAT +router.post('/emitir', strictLimit, facturacionController.emitir); +router.post('/cancelar/:uuid', strictLimit, facturacionController.cancelar); + +// Descargas +router.get('/pdf/:id', facturacionController.downloadPdf); +router.get('/xml/:id', facturacionController.downloadXml); + +// Timbres +router.get('/timbres', facturacionController.getTimbres); +router.get('/timbres/paquetes-catalogo', facturacionController.getPaquetesCatalogo); +// 10/h — cada compra crea MP Preference (side-effect en tercero) +router.post('/timbres/paquetes/comprar', strictLimit, facturacionController.comprarPaquete); +// Admin global: catálogo completo + editar precios +router.get('/timbres/paquetes-catalogo/admin', facturacionController.getPaquetesCatalogoAdmin); +router.put('/timbres/paquetes-catalogo/:id', facturacionController.updatePaqueteCatalogo); + +// Personalización (logo, color) +router.get('/customization', facturacionController.getCustomization); +router.post('/logo', facturacionController.uploadLogo); +router.put('/color', facturacionController.updateColor); + +// Datos fiscales del tenant +router.get('/datos-fiscales', facturacionController.getDatosFiscales); +router.put('/datos-fiscales', facturacionController.updateDatosFiscales); + +// Preferencias de auto-facturación de pagos de suscripción (Fase 2) +router.get('/preferencias-facturacion', facturacionController.getPreferenciasFacturacion); +router.put('/preferencias-facturacion', facturacionController.updatePreferenciasFacturacion); + +// Búsqueda de RFCs para autocompletar +router.get('/rfcs/search', facturacionController.searchRfcs); + +// Búsqueda de conceptos previos +router.get('/conceptos/search', facturacionController.searchConceptos); + +// CFDIs PPD pendientes de pago por RFC +router.get('/cfdis-ppd', facturacionController.getCfdisPpdPendientes); + +// CFDIs emitidos por el contribuyente al receptor (para sección "CFDIs relacionados") +router.get('/cfdis-relacionables', facturacionController.getCfdisRelacionables); + +export { router as facturacionRoutes }; diff --git a/apps/api/src/routes/fiel.routes.ts b/apps/api/src/routes/fiel.routes.ts new file mode 100644 index 0000000..177b5a3 --- /dev/null +++ b/apps/api/src/routes/fiel.routes.ts @@ -0,0 +1,20 @@ +import { Router, type IRouter } from 'express'; +import * as fielController from '../controllers/fiel.controller.js'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +// POST /api/fiel/upload - Subir credenciales FIEL +router.post('/upload', fielController.upload); + +// GET /api/fiel/status - Obtener estado de la FIEL +router.get('/status', fielController.status); + +// DELETE /api/fiel - Eliminar credenciales FIEL +router.delete('/', fielController.remove); + +export default router; diff --git a/apps/api/src/routes/impuestos.routes.ts b/apps/api/src/routes/impuestos.routes.ts new file mode 100644 index 0000000..7784cd6 --- /dev/null +++ b/apps/api/src/routes/impuestos.routes.ts @@ -0,0 +1,30 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { normalLimit } from '../middlewares/rate-limit.middleware.js'; +import * as impuestosController from '../controllers/impuestos.controller.js'; +import * as activosFijosController from '../controllers/activos-fijos.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(normalLimit); +router.use(tenantMiddleware); + +router.get('/iva/mensual', impuestosController.getIvaMensual); +router.get('/iva/resumen', impuestosController.getResumenIva); +router.get('/isr/mensual', impuestosController.getIsrMensual); +router.get('/isr/resumen', impuestosController.getResumenIsr); +router.get('/isr/resumen-desglosado', impuestosController.getResumenIsrDesglosado); +router.get('/isr/coeficiente', impuestosController.getCoeficiente); +router.put('/isr/coeficiente', impuestosController.setCoeficiente); + +// Activos fijos: vista informativa de deducción mensual proporcional. +// NO afecta gastos ni ISR — el SAT y el dashboard tratan estos CFDIs +// como gasto del periodo (igual que antes). +router.get('/activos-fijos', activosFijosController.list); +router.put('/activos-fijos/usos-excluidos', activosFijosController.setUsosExcluidos); +router.post('/activos-fijos/:cfdiId/baja', activosFijosController.darDeBaja); +router.delete('/activos-fijos/:cfdiId/baja', activosFijosController.revertirBaja); + +export { router as impuestosRoutes }; diff --git a/apps/api/src/routes/metricas.routes.ts b/apps/api/src/routes/metricas.routes.ts new file mode 100644 index 0000000..ae90a5c --- /dev/null +++ b/apps/api/src/routes/metricas.routes.ts @@ -0,0 +1,12 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/metricas.controller.js'; + +const router: IRouter = Router(); +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/mensuales', ctrl.getMensuales); + +export default router; diff --git a/apps/api/src/routes/notification-preferences.routes.ts b/apps/api/src/routes/notification-preferences.routes.ts new file mode 100644 index 0000000..aae9297 --- /dev/null +++ b/apps/api/src/routes/notification-preferences.routes.ts @@ -0,0 +1,14 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/notification-preferences.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/', ctrl.listPreferences); +router.put('/', ctrl.updatePreferences); + +export { router as notificationPreferencesRoutes }; diff --git a/apps/api/src/routes/papeleria.routes.ts b/apps/api/src/routes/papeleria.routes.ts new file mode 100644 index 0000000..91c3e03 --- /dev/null +++ b/apps/api/src/routes/papeleria.routes.ts @@ -0,0 +1,18 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/papeleria.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/', ctrl.list); +router.post('/', ctrl.upload); +router.get('/:id/download', ctrl.download); +router.post('/:id/aprobar', ctrl.aprobar); +router.post('/:id/rechazar', ctrl.rechazar); +router.delete('/:id', ctrl.eliminar); + +export { router as papeleriaRoutes }; diff --git a/apps/api/src/routes/plan-catalogo.routes.ts b/apps/api/src/routes/plan-catalogo.routes.ts new file mode 100644 index 0000000..a175ffd --- /dev/null +++ b/apps/api/src/routes/plan-catalogo.routes.ts @@ -0,0 +1,17 @@ +import { Router, type IRouter } from 'express'; +import * as ctrl from '../controllers/plan-catalogo.controller.js'; +import { authenticate } from '../middlewares/auth.middleware.js'; + +const router: IRouter = Router(); + +// Public endpoints — no auth needed (pricing page) +router.get('/', ctrl.getPlans); +router.get('/addons', ctrl.getAddons); + +// Catálogo despacho — admin only (require auth) +router.get('/despacho', authenticate, ctrl.listDespachoCatalogo); +router.patch('/despacho/:plan', authenticate, ctrl.updateDespachoCatalogo); + +router.get('/:codename', ctrl.getPlan); + +export default router; diff --git a/apps/api/src/routes/platform-staff.routes.ts b/apps/api/src/routes/platform-staff.routes.ts new file mode 100644 index 0000000..a3e6611 --- /dev/null +++ b/apps/api/src/routes/platform-staff.routes.ts @@ -0,0 +1,16 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import * as platformStaffController from '../controllers/platform-staff.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(authorize('owner', 'cfo')); + +// Solo platform_admin (verificado dentro del controller) +router.get('/', platformStaffController.listStaff); +router.get('/search', platformStaffController.searchUsers); +router.post('/grant', platformStaffController.grantRole); +router.post('/revoke', platformStaffController.revokeRole); + +export { router as platformStaffRoutes }; diff --git a/apps/api/src/routes/regimen.routes.ts b/apps/api/src/routes/regimen.routes.ts new file mode 100644 index 0000000..dc5de36 --- /dev/null +++ b/apps/api/src/routes/regimen.routes.ts @@ -0,0 +1,28 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { relaxedLimit } from '../middlewares/rate-limit.middleware.js'; +import * as regimenController from '../controllers/regimen.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(relaxedLimit); +router.use(tenantMiddleware); + +// GET /api/regimenes — catálogo completo +router.get('/', regimenController.getAllRegimenes); + +// GET /api/regimenes/activos — regímenes activos del tenant +router.get('/activos', regimenController.getActivos); + +// PUT /api/regimenes/activos — configurar regímenes activos +router.put('/activos', regimenController.setActivos); + +// GET /api/regimenes/ignorados — regímenes ignorados del tenant actual +router.get('/ignorados', regimenController.getIgnorados); + +// PUT /api/regimenes/ignorados — configurar regímenes ignorados +router.put('/ignorados', regimenController.setIgnorados); + +export { router as regimenRoutes }; diff --git a/apps/api/src/routes/reportes.routes.ts b/apps/api/src/routes/reportes.routes.ts new file mode 100644 index 0000000..4ae8ad6 --- /dev/null +++ b/apps/api/src/routes/reportes.routes.ts @@ -0,0 +1,24 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { checkPlanLimits } from '../middlewares/plan-limits.middleware.js'; +import { requireFeature } from '../middlewares/feature-gate.middleware.js'; +import { normalLimit } from '../middlewares/rate-limit.middleware.js'; +import * as reportesController from '../controllers/reportes.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(normalLimit); +router.use(tenantMiddleware); +router.use(checkPlanLimits); +router.use(requireFeature('reportes')); + +router.get('/estado-resultados', reportesController.getEstadoResultados); +router.get('/flujo-efectivo', reportesController.getFlujoEfectivo); +router.get('/comparativo', reportesController.getComparativo); +router.get('/concentrado-rfc', reportesController.getConcentradoRfc); +router.get('/cuentas-x-pagar', reportesController.getCuentasXPagar); +router.get('/cuentas-x-cobrar', reportesController.getCuentasXCobrar); + +export { router as reportesRoutes }; diff --git a/apps/api/src/routes/sat.routes.ts b/apps/api/src/routes/sat.routes.ts new file mode 100644 index 0000000..0dcf7ce --- /dev/null +++ b/apps/api/src/routes/sat.routes.ts @@ -0,0 +1,31 @@ +import { Router, type IRouter } from 'express'; +import * as satController from '../controllers/sat.controller.js'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import { veryStrictLimit } from '../middlewares/rate-limit.middleware.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +// POST /api/sat/sync - Iniciar sincronización manual (2/día por user, admin global exento) +router.post('/sync', veryStrictLimit, satController.start); + +// GET /api/sat/sync/status - Estado actual de sincronización +router.get('/sync/status', satController.status); + +// GET /api/sat/sync/history - Historial de sincronizaciones +router.get('/sync/history', satController.history); + +// GET /api/sat/sync/:id - Detalle de un job +router.get('/sync/:id', satController.jobDetail); + +// POST /api/sat/sync/:id/retry - Reintentar job fallido +router.post('/sync/:id/retry', satController.retry); + +// Admin-only cron endpoints (global admin verified in controller) +router.get('/cron', authorize('owner', 'cfo'), satController.cronInfo); +router.post('/cron/run', authorize('owner', 'cfo'), satController.runCron); + +export default router; diff --git a/apps/api/src/routes/subscription.routes.ts b/apps/api/src/routes/subscription.routes.ts new file mode 100644 index 0000000..6329aac --- /dev/null +++ b/apps/api/src/routes/subscription.routes.ts @@ -0,0 +1,37 @@ +import { Router, type IRouter } from 'express'; +import { authenticate, authorize } from '../middlewares/auth.middleware.js'; +import { strictLimit } from '../middlewares/rate-limit.middleware.js'; +import * as subscriptionController from '../controllers/subscription.controller.js'; + +const router: IRouter = Router(); + +// All endpoints require authentication + admin role +router.use(authenticate); +router.use(authorize('owner', 'cfo')); + +// (Endpoints /plans y /plans/:id eliminados con el modelo PlanPrice legacy. +// Para precios despacho usa /api/planes/despacho.) + +// Self-serve: el usuario actúa sobre el tenant de su JWT +router.post('/me/trial', subscriptionController.startMyTrial); +// 10/hora — crea preapprovals/preferences en MercadoPago (side-effect en tercero) +router.post('/me/subscribe', strictLimit, subscriptionController.subscribeMe); +router.post('/me/change', strictLimit, subscriptionController.changeMyPlan); +router.post('/me/upgrade', strictLimit, subscriptionController.upgradeMe); +router.post('/me/upgrade/cancel', subscriptionController.cancelMyPendingUpgrade); +router.post('/me/cancel', subscriptionController.cancelMySubscription); +router.post('/me/reactivate', subscriptionController.reactivateMe); +router.get('/me/addons', subscriptionController.getMyAddons); +router.post('/me/addons', strictLimit, subscriptionController.addMyAddon); +router.delete('/me/addons/:addonId', subscriptionController.cancelMyAddon); + +// Listar todas las suscripciones (global admin) +router.get('/', subscriptionController.getAllSubscriptions); + +// Admin subscription management (global admin verified in controller) +router.get('/:tenantId', subscriptionController.getSubscription); +router.post('/:tenantId/generate-link', subscriptionController.generatePaymentLink); +router.post('/:tenantId/mark-paid', subscriptionController.markAsPaid); +router.get('/:tenantId/payments', subscriptionController.getPayments); + +export { router as subscriptionRoutes }; diff --git a/apps/api/src/routes/tareas.routes.ts b/apps/api/src/routes/tareas.routes.ts new file mode 100644 index 0000000..271b429 --- /dev/null +++ b/apps/api/src/routes/tareas.routes.ts @@ -0,0 +1,20 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as ctrl from '../controllers/tareas.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); +router.use(tenantMiddleware); + +router.get('/', ctrl.listTareas); +router.post('/', ctrl.createTarea); +router.post('/seed', ctrl.seedDefaults); +router.patch('/:id', ctrl.updateTarea); +router.delete('/:id', ctrl.deleteTarea); + +router.post('/periodo/:id/completar', ctrl.completarPeriodo); +router.delete('/periodo/:id/completar', ctrl.descompletarPeriodo); + +export { router as tareasRoutes }; diff --git a/apps/api/src/routes/tenants.routes.ts b/apps/api/src/routes/tenants.routes.ts new file mode 100644 index 0000000..17764b9 --- /dev/null +++ b/apps/api/src/routes/tenants.routes.ts @@ -0,0 +1,20 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import * as tenantsController from '../controllers/tenants.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); + +// Self-serve multi-tenant (el caller actúa sobre sus propias memberships) +router.get('/mine', tenantsController.getMyTenants); +router.post('/mine', tenantsController.addMyTenant); + +// Admin global +router.get('/', tenantsController.getAllTenants); +router.get('/:id', tenantsController.getTenant); +router.post('/', tenantsController.createTenant); +router.put('/:id', tenantsController.updateTenant); +router.delete('/:id', tenantsController.deleteTenant); + +export { router as tenantsRoutes }; diff --git a/apps/api/src/routes/usuarios.routes.ts b/apps/api/src/routes/usuarios.routes.ts new file mode 100644 index 0000000..0dbda62 --- /dev/null +++ b/apps/api/src/routes/usuarios.routes.ts @@ -0,0 +1,29 @@ +import { Router, type IRouter } from 'express'; +import { authenticate } from '../middlewares/auth.middleware.js'; +import { tenantMiddleware } from '../middlewares/tenant.middleware.js'; +import * as usuariosController from '../controllers/usuarios.controller.js'; + +const router: IRouter = Router(); + +router.use(authenticate); + +// Rutas por tenant +router.get('/', usuariosController.getUsuarios); +router.post('/invite', usuariosController.inviteUsuario); +router.patch('/:id', usuariosController.updateUsuario); +router.delete('/:id', usuariosController.deleteUsuario); + +// Cliente accesos (requires tenantPool) +router.get('/:id/accesos', tenantMiddleware, usuariosController.getClienteAccesos); +router.post('/:id/accesos', tenantMiddleware, usuariosController.setClienteAccesos); + +// Supervisor de auxiliar (requires tenantPool) +router.get('/:id/supervisor', tenantMiddleware, usuariosController.getSupervisor); +router.put('/:id/supervisor', tenantMiddleware, usuariosController.updateSupervisor); + +// Rutas globales (solo admin global) +router.get('/global/all', usuariosController.getAllUsuarios); +router.patch('/global/:id', usuariosController.updateUsuarioGlobal); +router.delete('/global/:id', usuariosController.deleteUsuarioGlobal); + +export { router as usuariosRoutes }; diff --git a/apps/api/src/routes/webhook.routes.ts b/apps/api/src/routes/webhook.routes.ts new file mode 100644 index 0000000..7096d02 --- /dev/null +++ b/apps/api/src/routes/webhook.routes.ts @@ -0,0 +1,9 @@ +import { Router, type IRouter } from 'express'; +import { handleMercadoPagoWebhook } from '../controllers/webhook.controller.js'; + +const router: IRouter = Router(); + +// Public endpoint — no auth middleware +router.post('/mercadopago', handleMercadoPagoWebhook); + +export { router as webhookRoutes }; diff --git a/apps/api/src/services/_shared/cfdi-filters.ts b/apps/api/src/services/_shared/cfdi-filters.ts new file mode 100644 index 0000000..98eaf8a --- /dev/null +++ b/apps/api/src/services/_shared/cfdi-filters.ts @@ -0,0 +1,148 @@ +/** + * Helpers para construir fragmentos AND adicionales en WHERE clauses según + * los toggles "Considerar activos" y "Considerar NCs" de la UI de impuestos. + * + * - considerarActivos === false → excluir facturas relacionadas a activos: + * 1) I directo con uso_cfdi I01-I08. + * 2) P pagando una I-activo (vía uuid_relacionado). + * 3) E que referencia una I-activo o una P-de-activo (vía cfdis_relacionados, + * cualquier tipoRel — cubre NCs tipoRel=01, devoluciones tipoRel=03, etc.). + * 4) Anticipo (I PUE/PPD) que es referenciado por una I/07 PPD con uso_cfdi + * de activo (la I/07 "aplica" el anticipo al activo, así que el anticipo + * también es para un activo). + * - considerarNCs === false → excluir TODAS las facturas tipo E (cualquier + * tipo_relacion). Además, los callers que aplican la compensación I/07 PPD + * ↔ E (ingresos Grupo 1 y deducciones) deben saltarla cuando este flag es + * false (la compensación lee valores de E para compensar; sin E, no aplica). + * + * Cuando ambos son true (default backend = "include todo"), retorna string + * vacío. Esto preserva el comportamiento histórico para callers que no pasan + * los flags (ej. dashboard, reportes). + * + * Las versiones `Alias` se usan en subqueries con alias de tabla + * (ej. `cfdis e` en SUM_E_REFERENCING_*). El filtro de NCs aplica directo; + * el de activos aplica también pero algunos predicados son no-op funcional + * en subqueries que filtran por tipo_comprobante específico (Postgres los + * optimiza away). + */ + +const ACTIVOS_USOS = "('I01','I02','I03','I04','I05','I06','I07','I08')"; + +/** + * Predicado SQL que detecta si el row actual (sin alias de tabla, asume + * `FROM cfdis`) referencia un activo directamente (I), indirectamente vía + * pago (P→I), o transitivamente vía relación (E→I, E→P→I). + * + * IMPORTANTE — qualifying outer refs: dentro de los subqueries `cfdis i_act` + * y `cfdis r_act`, la tabla interna también tiene columnas `uuid_relacionado` + * y `cfdis_relacionados`. Una referencia no-qualificada las resolvería a las + * columnas internas (NO al row outer), volviendo el predicado a no-op. + * Por eso usamos `cfdis.uuid_relacionado` y `cfdis.cfdis_relacionados` + * explícitamente — fuerza la resolución al outer. + */ +function activosExclusionNoAlias(): string { + return ` + AND NOT (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS_USOS}) + AND NOT (tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS_USOS} + )) + AND NOT (tipo_comprobante = 'E' AND cfdis.cfdis_relacionados IS NOT NULL AND EXISTS ( + SELECT 1 FROM cfdis r_act + WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|')) + AND ( + (r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS_USOS}) + OR (r_act.tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis pi_act + WHERE LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado) + AND pi_act.tipo_comprobante = 'I' + AND pi_act.uso_cfdi IN ${ACTIVOS_USOS} + )) + ) + )) + AND NOT (tipo_comprobante = 'I' AND EXISTS ( + -- Anticipo: CFDI tipo I (puede no tener uso_cfdi de activo) que es + -- referenciado por una I/07 PPD con uso_cfdi de activo. La I/07 PPD + -- "aplica" el anticipo a la compra del activo, así que el anticipo + -- también debe filtrarse cuando se desactiva "Considerar activos". + SELECT 1 FROM cfdis i07_act + WHERE i07_act.tipo_comprobante = 'I' + AND i07_act.metodo_pago = 'PPD' + AND COALESCE(i07_act.cfdi_tipo_relacion, '') = '07' + AND i07_act.uso_cfdi IN ${ACTIVOS_USOS} + AND i07_act.status NOT IN ('Cancelado', '0') + AND i07_act.cfdis_relacionados IS NOT NULL + AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(i07_act.cfdis_relacionados), '|')) + )) + `.trim(); +} + +/** + * Misma lógica que activosExclusionNoAlias pero referenciando columnas con + * el alias de tabla externo (ej. 'e' en `FROM cfdis e`). + */ +function activosExclusionAlias(alias: string): string { + return ` + AND NOT (${alias}.tipo_comprobante = 'I' AND ${alias}.uso_cfdi IN ${ACTIVOS_USOS}) + AND NOT (${alias}.tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis i_act + WHERE LOWER(i_act.uuid) = LOWER(${alias}.uuid_relacionado) + AND i_act.tipo_comprobante = 'I' + AND i_act.uso_cfdi IN ${ACTIVOS_USOS} + )) + AND NOT (${alias}.tipo_comprobante = 'E' AND ${alias}.cfdis_relacionados IS NOT NULL AND EXISTS ( + SELECT 1 FROM cfdis r_act + WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(${alias}.cfdis_relacionados), '|')) + AND ( + (r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS_USOS}) + OR (r_act.tipo_comprobante = 'P' AND EXISTS ( + SELECT 1 FROM cfdis pi_act + WHERE LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado) + AND pi_act.tipo_comprobante = 'I' + AND pi_act.uso_cfdi IN ${ACTIVOS_USOS} + )) + ) + )) + AND NOT (${alias}.tipo_comprobante = 'I' AND EXISTS ( + SELECT 1 FROM cfdis i07_act + WHERE i07_act.tipo_comprobante = 'I' + AND i07_act.metodo_pago = 'PPD' + AND COALESCE(i07_act.cfdi_tipo_relacion, '') = '07' + AND i07_act.uso_cfdi IN ${ACTIVOS_USOS} + AND i07_act.status NOT IN ('Cancelado', '0') + AND i07_act.cfdis_relacionados IS NOT NULL + AND LOWER(${alias}.uuid) = ANY(string_to_array(LOWER(i07_act.cfdis_relacionados), '|')) + )) + `.trim(); +} + +export function buildExtraFilters( + considerarActivos: boolean, + considerarNCs: boolean, +): string { + const parts: string[] = []; + if (!considerarActivos) { + parts.push(activosExclusionNoAlias()); + } + if (!considerarNCs) { + parts.push(`AND NOT (tipo_comprobante = 'E')`); + } + return parts.length > 0 ? ' ' + parts.join(' ') : ''; +} + +export function buildExtraFiltersAlias( + alias: string, + considerarActivos: boolean, + considerarNCs: boolean, +): string { + const parts: string[] = []; + if (!considerarActivos) { + parts.push(activosExclusionAlias(alias)); + } + if (!considerarNCs) { + parts.push(`AND NOT (${alias}.tipo_comprobante = 'E')`); + } + return parts.length > 0 ? ' ' + parts.join(' ') : ''; +} diff --git a/apps/api/src/services/activos-fijos.service.ts b/apps/api/src/services/activos-fijos.service.ts new file mode 100644 index 0000000..65f6e82 --- /dev/null +++ b/apps/api/src/services/activos-fijos.service.ts @@ -0,0 +1,265 @@ +import type { Pool } from 'pg'; +import { resolveContribuyenteContext } from '../utils/contribuyente-context.js'; + +/** + * Activos fijos: CFDIs tipo I con uso_cfdi I01-I08 recibidos por el + * contribuyente bajo régimen fiscal aplicable. Vista INFORMATIVA — no + * modifica gastos ni ISR (el sistema sigue tratándolos como gasto del + * periodo, igual que el SAT). Esta vista permite que el contador haga + * su seguimiento de deducción mensual proporcional y decida si la + * aplica o no en su declaración. + * + * % anual de deducción según LISR Art. 34. Mensual = anual / 12. + */ +export const PORCENTAJES_ANUALES: Record = { + I01: { concepto: 'Construcciones', pct: 0.05 }, + I02: { concepto: 'Mobiliario y equipo de oficina', pct: 0.10 }, + I03: { concepto: 'Equipo de transporte', pct: 0.25 }, + I04: { concepto: 'Equipo de cómputo y accesorios', pct: 0.30 }, + I05: { concepto: 'Dados, troqueles, moldes, matrices', pct: 0.35 }, + I06: { concepto: 'Comunicaciones telefónicas', pct: 0.10 }, + I07: { concepto: 'Comunicaciones satelitales', pct: 0.08 }, + I08: { concepto: 'Otra maquinaria y equipo', pct: 0.10 }, +}; + +const USOS_CFDI = Object.keys(PORCENTAJES_ANUALES); +const REGIMENES_APLICABLES = ['601', '606', '611', '612', '625', '626']; + +export type EstadoActivo = 'activo' | 'agotado' | 'baja_venta' | 'baja_desecho' | 'baja_otro'; + +export interface ActivoFijoItem { + cfdiId: number; + uuid: string; + fechaEmision: string; + rfcEmisor: string; + nombreEmisor: string; + usoCfdi: string; + concepto: string; + porcentajeAnual: number; + porcentajeMensual: number; + total: number; + iva: number; + moi: number; + acumuladoHastaMesAnterior: number; + acreditableEsteMes: number; + saldoPendiente: number; + estado: EstadoActivo; + baja: { fechaBaja: string; motivo: string; comentario: string | null } | null; +} + +export interface ActivosFijosTotales { + cantidad: number; + totalMoi: number; + totalAcumuladoPrevio: number; + totalEsteMes: number; + totalSaldoPendiente: number; + cantidadActivos: number; + cantidadAgotados: number; + cantidadDeBaja: number; +} + +function clamp(v: number, lo: number, hi: number): number { + return Math.max(lo, Math.min(hi, v)); +} + +function diffMeses(start: Date, end: Date): number { + return (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth()) + 1; +} + +export async function listActivosFijos( + pool: Pool, + tenantId: string, + año: number, + mes: number, + contribuyenteId?: string | null, + filtroEstado?: 'todos' | 'activos' | 'baja' | 'agotados', +): Promise<{ items: ActivoFijoItem[]; totales: ActivosFijosTotales; usosExcluidos: string[] }> { + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esReceptor = ctx.esReceptor; + const esPM = ctx.rfcLength === 12; + + // Lee usos excluidos del contribuyente (lista de claves a saltarse, ej. + // I06/I07 cuando son gastos regulares y no activos fijos reales). + let usosExcluidos: string[] = []; + if (contribuyenteId) { + const { rows } = await pool.query<{ activos_fijos_usos_excluidos: string[] | null }>( + `SELECT activos_fijos_usos_excluidos FROM contribuyentes WHERE entidad_id = $1`, + [contribuyenteId.replace(/[^a-f0-9-]/gi, '')], + ); + usosExcluidos = (rows[0]?.activos_fijos_usos_excluidos ?? []).filter(u => USOS_CFDI.includes(u)); + } + const usosAplicables = USOS_CFDI.filter(u => !usosExcluidos.includes(u)); + + // Filtro de régimen: 626 solo aplica si el contribuyente es PM. + const regsAplicables = esPM ? REGIMENES_APLICABLES : REGIMENES_APLICABLES.filter(r => r !== '626'); + + const usosArray = `ARRAY[${usosAplicables.map(u => `'${u}'`).join(',')}]`; + const regsArray = `ARRAY[${regsAplicables.map(r => `'${r}'`).join(',')}]`; + + const { rows } = await pool.query( + `SELECT c.id AS cfdi_id, c.uuid, c.fecha_emision, c.rfc_emisor, c.nombre_emisor, + c.uso_cfdi, c.total_mxn, c.iva_traslado_mxn, c.ieps_traslado_mxn, + c.impuestos_locales_trasladado_mxn, c.regimen_fiscal_receptor, + b.fecha_baja, b.motivo, b.comentario + FROM cfdis c + LEFT JOIN activos_fijos_baja b ON b.cfdi_id = c.id + WHERE ${esReceptor} + AND c.tipo_comprobante = 'I' + AND c.uso_cfdi = ANY(${usosArray}) + AND c.regimen_fiscal_receptor = ANY(${regsArray}) + AND c.status NOT IN ('Cancelado','0') + ORDER BY c.fecha_emision DESC`, + ); + + const items: ActivoFijoItem[] = []; + const periodoFin = new Date(año, mes - 1, 1); // primer día del mes filtrado + + for (const r of rows) { + const fechaEmision = new Date(r.fecha_emision); + const moi = Number(r.total_mxn ?? 0) + - Number(r.iva_traslado_mxn ?? 0) + - Number(r.ieps_traslado_mxn ?? 0) + - Number(r.impuestos_locales_trasladado_mxn ?? 0); + const meta = PORCENTAJES_ANUALES[r.uso_cfdi]; + if (!meta) continue; + const pctAnual = meta.pct; + const pctMensual = pctAnual / 12; + + // Fecha de baja (si existe) limita los meses aplicables + const fechaBaja = r.fecha_baja ? new Date(r.fecha_baja) : null; + + // Mes ancla del periodo filtrado + const mesEjAnchor = new Date(año, mes - 1, 1); + const mesAdqAnchor = new Date(fechaEmision.getFullYear(), fechaEmision.getMonth(), 1); + + // Meses transcurridos hasta el mes filtrado (incluido) + let mesesHasta = diffMeses(mesAdqAnchor, mesEjAnchor); + let mesesHastaPrev = mesesHasta - 1; + + // Recortar si hay baja: máximo el mes de la baja inclusive + if (fechaBaja) { + const mesBaja = new Date(fechaBaja.getFullYear(), fechaBaja.getMonth(), 1); + const mesesHastaBaja = diffMeses(mesAdqAnchor, mesBaja); + mesesHasta = Math.min(mesesHasta, mesesHastaBaja); + mesesHastaPrev = Math.min(mesesHastaPrev, mesesHastaBaja); + } + + mesesHasta = Math.max(0, mesesHasta); + mesesHastaPrev = Math.max(0, mesesHastaPrev); + + const acumHasta = clamp(moi * pctMensual * mesesHasta, 0, moi); + const acumPrev = clamp(moi * pctMensual * mesesHastaPrev, 0, moi); + const acreditable = Math.max(0, acumHasta - acumPrev); + const saldo = Math.max(0, moi - acumHasta); + + let estado: EstadoActivo = 'activo'; + if (fechaBaja) { + estado = `baja_${r.motivo}` as EstadoActivo; + } else if (saldo === 0) { + estado = 'agotado'; + } + + if (filtroEstado === 'activos' && estado !== 'activo') continue; + if (filtroEstado === 'agotados' && estado !== 'agotado') continue; + if (filtroEstado === 'baja' && !estado.startsWith('baja_')) continue; + + items.push({ + cfdiId: r.cfdi_id, + uuid: r.uuid, + fechaEmision: fechaEmision.toISOString().slice(0, 10), + rfcEmisor: r.rfc_emisor, + nombreEmisor: r.nombre_emisor ?? '', + usoCfdi: r.uso_cfdi, + concepto: meta.concepto, + porcentajeAnual: pctAnual, + porcentajeMensual: pctMensual, + total: Number(r.total_mxn ?? 0), + iva: Number(r.iva_traslado_mxn ?? 0), + moi, + acumuladoHastaMesAnterior: Math.round(acumPrev * 100) / 100, + acreditableEsteMes: Math.round(acreditable * 100) / 100, + saldoPendiente: Math.round(saldo * 100) / 100, + estado, + baja: fechaBaja + ? { fechaBaja: fechaBaja.toISOString().slice(0, 10), motivo: r.motivo, comentario: r.comentario } + : null, + }); + } + + const totales: ActivosFijosTotales = { + cantidad: items.length, + totalMoi: 0, + totalAcumuladoPrevio: 0, + totalEsteMes: 0, + totalSaldoPendiente: 0, + cantidadActivos: 0, + cantidadAgotados: 0, + cantidadDeBaja: 0, + }; + for (const i of items) { + totales.totalMoi += i.moi; + totales.totalAcumuladoPrevio += i.acumuladoHastaMesAnterior; + totales.totalEsteMes += i.acreditableEsteMes; + totales.totalSaldoPendiente += i.saldoPendiente; + if (i.estado === 'activo') totales.cantidadActivos++; + else if (i.estado === 'agotado') totales.cantidadAgotados++; + else totales.cantidadDeBaja++; + } + totales.totalMoi = Math.round(totales.totalMoi * 100) / 100; + totales.totalAcumuladoPrevio = Math.round(totales.totalAcumuladoPrevio * 100) / 100; + totales.totalEsteMes = Math.round(totales.totalEsteMes * 100) / 100; + totales.totalSaldoPendiente = Math.round(totales.totalSaldoPendiente * 100) / 100; + + return { items, totales, usosExcluidos }; +} + +/** Lee los usos CFDI excluidos para un contribuyente. */ +export async function getUsosExcluidos(pool: Pool, contribuyenteId: string): Promise { + const { rows } = await pool.query<{ activos_fijos_usos_excluidos: string[] | null }>( + `SELECT activos_fijos_usos_excluidos FROM contribuyentes WHERE entidad_id = $1`, + [contribuyenteId.replace(/[^a-f0-9-]/gi, '')], + ); + return (rows[0]?.activos_fijos_usos_excluidos ?? []).filter(u => USOS_CFDI.includes(u)); +} + +/** Guarda la lista de usos excluidos (filtra a I01-I08 y deduplica). */ +export async function setUsosExcluidos( + pool: Pool, + contribuyenteId: string, + usos: string[], +): Promise { + const valid = [...new Set(usos.filter(u => USOS_CFDI.includes(u)))]; + await pool.query( + `UPDATE contribuyentes SET activos_fijos_usos_excluidos = $2::jsonb WHERE entidad_id = $1`, + [contribuyenteId.replace(/[^a-f0-9-]/gi, ''), JSON.stringify(valid)], + ); + return valid; +} + +export async function darDeBaja( + pool: Pool, + cfdiId: number, + fechaBaja: string, + motivo: 'venta' | 'desecho' | 'otro', + userId: string, + comentario: string | null, +): Promise { + await pool.query( + `INSERT INTO activos_fijos_baja (cfdi_id, fecha_baja, motivo, comentario, dado_de_baja_por) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (cfdi_id) DO UPDATE + SET fecha_baja = EXCLUDED.fecha_baja, + motivo = EXCLUDED.motivo, + comentario = EXCLUDED.comentario, + dado_de_baja_por = EXCLUDED.dado_de_baja_por`, + [cfdiId, fechaBaja, motivo, comentario, userId], + ); +} + +export async function revertirBaja(pool: Pool, cfdiId: number): Promise { + const { rowCount } = await pool.query( + `DELETE FROM activos_fijos_baja WHERE cfdi_id = $1`, + [cfdiId], + ); + return (rowCount ?? 0) > 0; +} diff --git a/apps/api/src/services/admin-clientes.service.ts b/apps/api/src/services/admin-clientes.service.ts new file mode 100644 index 0000000..2d5b20b --- /dev/null +++ b/apps/api/src/services/admin-clientes.service.ts @@ -0,0 +1,147 @@ +/** + * Métricas para la página de Gestión de Clientes (admin global). + * + * Distinto de `admin-dashboard.service.ts` que provee KPIs generales fijos + * a 30 días. Aquí el rango es parametrizado y se enfoca en suscripciones + * activas por plan, ingresos del periodo, clientes que no renovaron, y + * usuarios por cliente. + */ +import { prisma } from '../config/database.js'; + +export interface ClientesStatsRange { + from: Date; + to: Date; +} + +export interface ClientesStats { + /** Suscripciones activas (status=authorized) agrupadas por plan. */ + suscripcionesPorPlan: Array<{ plan: string; count: number }>; + /** Ingresos del periodo (payments approved con createdAt en rango). */ + ingresos: { + total: number; + paymentsCount: number; + }; + /** Clientes cuya suscripción venció en el rango y NO se renovó. */ + noRenovaciones: Array<{ + tenantId: string; + tenantNombre: string; + rfc: string; + plan: string; + currentPeriodEnd: string; + statusActual: string; + }>; + /** Cuenta de usuarios activos por tenant. */ + usuariosPorCliente: Array<{ + tenantId: string; + tenantNombre: string; + rfc: string; + activeUsers: number; + owners: number; + }>; +} + +export async function getClientesStats(range: ClientesStatsRange): Promise { + // 1) Suscripciones activas agrupadas por plan + const subsByPlan = await prisma.subscription.groupBy({ + by: ['plan'], + where: { status: 'authorized' }, + _count: { _all: true }, + }); + const suscripcionesPorPlan = subsByPlan.map(s => ({ + plan: String(s.plan), + count: s._count._all, + })); + + // 2) Ingresos del periodo + const payments = await prisma.payment.aggregate({ + where: { + status: 'approved', + createdAt: { gte: range.from, lte: range.to }, + }, + _sum: { amount: true }, + _count: true, + }); + const ingresos = { + total: Number(payments._sum.amount || 0), + paymentsCount: payments._count, + }; + + // 3) Clientes que NO renovaron: subs cuyo currentPeriodEnd cae en el rango + // y que están en status terminal (cancelled, trial_expired, paused) o sin + // payment posterior aprobado. Nota: un sub `authorized` con periodEnd + // pasado es un "se renovó automáticamente" — para detectar no-renovaciones + // miramos status efectivo + ausencia de payment en los siguientes 7 días. + const subsExpiradas = await prisma.subscription.findMany({ + where: { + currentPeriodEnd: { gte: range.from, lte: range.to }, + status: { in: ['cancelled', 'trial_expired', 'paused'] }, + }, + select: { + tenantId: true, + plan: true, + status: true, + currentPeriodEnd: true, + tenant: { select: { id: true, nombre: true, rfc: true } }, + }, + }); + const noRenovaciones = subsExpiradas.map(s => ({ + tenantId: s.tenantId, + tenantNombre: s.tenant?.nombre ?? '', + rfc: s.tenant?.rfc ?? '', + plan: String(s.plan), + currentPeriodEnd: s.currentPeriodEnd?.toISOString() ?? '', + statusActual: s.status, + })); + + // 4) Usuarios por cliente (memberships activos por tenant) + const memberships = await prisma.tenantMembership.findMany({ + where: { active: true }, + select: { tenantId: true, isOwner: true, tenant: { select: { nombre: true, rfc: true } } }, + }); + const byTenant = new Map(); + for (const m of memberships) { + const ent = byTenant.get(m.tenantId) ?? { + nombre: m.tenant?.nombre ?? '', + rfc: m.tenant?.rfc ?? '', + total: 0, + owners: 0, + }; + ent.total += 1; + if (m.isOwner) ent.owners += 1; + byTenant.set(m.tenantId, ent); + } + const usuariosPorCliente = Array.from(byTenant.entries()).map(([tenantId, v]) => ({ + tenantId, + tenantNombre: v.nombre, + rfc: v.rfc, + activeUsers: v.total, + owners: v.owners, + })).sort((a, b) => b.activeUsers - a.activeUsers); + + return { suscripcionesPorPlan, ingresos, noRenovaciones, usuariosPorCliente }; +} + +/** Lista usuarios activos de un tenant (con email + rol). Para el drill-down de la UI. */ +export async function getTenantUsuarios(tenantId: string) { + const memberships = await prisma.tenantMembership.findMany({ + where: { tenantId, active: true }, + select: { + isOwner: true, + joinedAt: true, + user: { select: { id: true, email: true, nombre: true, active: true, lastLogin: true } }, + rol: { select: { nombre: true } }, + }, + orderBy: { joinedAt: 'asc' }, + }); + return memberships + .filter(m => m.user.active) + .map(m => ({ + userId: m.user.id, + email: m.user.email, + nombre: m.user.nombre, + rol: m.rol?.nombre ?? 'sin_rol', + isOwner: m.isOwner, + joinedAt: m.joinedAt.toISOString(), + lastLogin: m.user.lastLogin?.toISOString() ?? null, + })); +} diff --git a/apps/api/src/services/admin-dashboard.service.ts b/apps/api/src/services/admin-dashboard.service.ts new file mode 100644 index 0000000..665b23d --- /dev/null +++ b/apps/api/src/services/admin-dashboard.service.ts @@ -0,0 +1,89 @@ +import { prisma } from '../config/database.js'; + +export async function getDashboardMetrics() { + const now = new Date(); + const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + + const [ + totalTenants, + activeTenants, + trialTenants, + cancelledSubs, + recentSignups, + connectorStatuses, + payments, + ] = await Promise.all([ + prisma.tenant.count(), + prisma.tenant.count({ where: { active: true } }), + prisma.tenant.count({ where: { trialEndsAt: { gt: now } } }), + prisma.subscription.count({ where: { status: 'cancelled' } }), + prisma.tenant.count({ where: { createdAt: { gte: thirtyDaysAgo } } }), + prisma.tenant.findMany({ + where: { dbMode: 'BYO', connectorTunnelHostname: { not: null } }, + select: { id: true, nombre: true, rfc: true, connectorLastSeen: true, connectorVersion: true }, + }), + prisma.payment.aggregate({ + where: { status: 'approved', createdAt: { gte: thirtyDaysAgo } }, + _sum: { amount: true }, + _count: true, + }), + ]); + + const connectors = connectorStatuses.map(t => { + let status: 'connected' | 'degraded' | 'disconnected' = 'disconnected'; + if (t.connectorLastSeen) { + const diff = now.getTime() - t.connectorLastSeen.getTime(); + if (diff < 60_000) status = 'connected'; + else if (diff < 300_000) status = 'degraded'; + } + return { id: t.id, nombre: t.nombre, rfc: t.rfc, status, lastSeen: t.connectorLastSeen?.toISOString(), version: t.connectorVersion }; + }); + + const connectorsDown = connectors.filter(c => c.status === 'disconnected').length; + + return { + tenants: { total: totalTenants, active: activeTenants, trial: trialTenants, cancelled: cancelledSubs }, + signupsLast30Days: recentSignups, + revenue: { last30Days: Number(payments._sum.amount || 0), paymentsCount: payments._count }, + connectors: { total: connectors.length, down: connectorsDown, list: connectors }, + }; +} + +export async function listAllDespachos(filters?: { vertical?: string; status?: string; search?: string }) { + const where: any = {}; + if (filters?.vertical) where.verticalProfile = filters.vertical; + if (filters?.status === 'active') where.active = true; + if (filters?.status === 'inactive') where.active = false; + if (filters?.search) { + where.OR = [ + { nombre: { contains: filters.search, mode: 'insensitive' } }, + { rfc: { contains: filters.search, mode: 'insensitive' } }, + ]; + } + + const tenants = await prisma.tenant.findMany({ + where, + select: { + id: true, nombre: true, rfc: true, plan: true, active: true, verticalProfile: true, + dbMode: true, connectorLastSeen: true, createdAt: true, trialEndsAt: true, + }, + orderBy: { createdAt: 'desc' }, + take: 100, + }); + + return tenants.map(t => ({ + ...t, + connectorLastSeen: t.connectorLastSeen?.toISOString(), + createdAt: t.createdAt.toISOString(), + trialEndsAt: t.trialEndsAt?.toISOString(), + })); +} + +export async function getRecentActivity(limit = 20) { + const logs = await prisma.auditLog.findMany({ + where: { action: { in: ['user.register', 'user.login', 'subscription.created', 'subscription.cancelled', 'subscription.upgraded', 'price.updated'] } }, + orderBy: { createdAt: 'desc' }, + take: limit, + }); + return logs; +} diff --git a/apps/api/src/services/alertas-auto.service.ts b/apps/api/src/services/alertas-auto.service.ts new file mode 100644 index 0000000..7d2ac26 --- /dev/null +++ b/apps/api/src/services/alertas-auto.service.ts @@ -0,0 +1,683 @@ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; +import { getRegimenesActivosClavesEfectivos } from './regimen.service.js'; +import { contarTareasProximasVencer } from './tareas.service.js'; + +const VIGENTE = `status NOT IN ('Cancelado', '0')`; + +/** Sanitize a contribuyente UUID for safe inline SQL injection */ +function sanitizeUuid(id: string): string { + return id.replace(/[^a-f0-9-]/gi, ''); +} + +export interface AlertaAuto { + id: string; + tipo: string; + titulo: string; + mensaje: string; + prioridad: 'alta' | 'media' | 'baja'; + detalle?: string; // ruta para drill-down + valor?: number; +} + +/** + * CFDI con discrepancia: facturas recibidas donde regimen_fiscal_receptor + * no coincide con los regímenes activos del tenant. + */ +async function alertaDiscrepanciaRegimen( + pool: Pool, + tenantId: string, + contribuyenteId?: string | null, +): Promise { + const activos = await getRegimenesActivosClavesEfectivos(tenantId, pool, contribuyenteId); + if (activos.length === 0) return null; + + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + + const { rows: [r] } = await pool.query(` + SELECT COUNT(*)::int as total + FROM cfdis + WHERE type = 'RECIBIDO' AND status = 'Vigente' + AND fecha_cancelacion IS NULL + AND regimen_fiscal_receptor IS NOT NULL + AND regimen_fiscal_receptor != ALL($1) + AND id NOT IN (SELECT cfdi_id FROM cfdi_descartados WHERE tipo_alerta = 'discrepancia-regimen') + ${cf} + `, [activos]); + + const total = r?.total || 0; + if (total === 0) return null; + + return { + id: 'discrepancia-regimen', + tipo: 'discrepancia', + titulo: 'CFDI con Discrepancia de Regimen', + mensaje: `${total} factura(s) recibida(s) con regimen fiscal del receptor que no coincide con los regimenes activos.`, + prioridad: 'alta', + detalle: '/alertas/discrepancia-regimen', + valor: total, + }; +} + +/** + * Calcula el Índice de Herfindahl-Hirschman (IHH). + * IHH = Σ (cuota_de_mercado_i)^2 × 10000 + */ +async function calcularIHH( + pool: Pool, + type: 'EMITIDO' | 'RECIBIDO', + contribuyenteId?: string | null, +): Promise { + const rfcField = type === 'EMITIDO' ? 'rfc_receptor' : 'rfc_emisor'; + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + + const { rows } = await pool.query(` + SELECT ${rfcField} as rfc, SUM(total_mxn) as total + FROM cfdis + WHERE type = $1 AND tipo_comprobante = 'I' AND ${VIGENTE} + AND total_mxn > 0 + ${cf} + GROUP BY ${rfcField} + `, [type]); + + if (rows.length === 0) return 0; + + const totalGeneral = rows.reduce((s: number, r: any) => s + Number(r.total), 0); + if (totalGeneral === 0) return 0; + + let ihh = 0; + for (const row of rows) { + const cuota = Number(row.total) / totalGeneral; + ihh += cuota * cuota; + } + + return Math.round(ihh * 10000); +} + +/** + * Concentración de clientes: IHH >= 2500 en facturas emitidas + */ +async function alertaConcentracionClientes(pool: Pool, contribuyenteId?: string | null): Promise { + const ihh = await calcularIHH(pool, 'EMITIDO', contribuyenteId); + if (ihh < 2500) return null; + + return { + id: 'concentracion-clientes', + tipo: 'concentracion', + titulo: 'Concentracion de Clientes', + mensaje: `El indice HHI de clientes es ${ihh.toLocaleString()} (>=2500 indica alta concentracion). Dependencia excesiva en pocos clientes.`, + prioridad: ihh >= 5000 ? 'alta' : 'media', + detalle: '/alertas/concentracion-clientes', + valor: ihh, + }; +} + +/** + * Concentración de proveedores: IHH >= 2500 en facturas recibidas + */ +async function alertaConcentracionProveedores(pool: Pool, contribuyenteId?: string | null): Promise { + const ihh = await calcularIHH(pool, 'RECIBIDO', contribuyenteId); + if (ihh < 2500) return null; + + return { + id: 'concentracion-proveedores', + tipo: 'concentracion', + titulo: 'Concentracion de Proveedores', + mensaje: `El indice HHI de proveedores es ${ihh.toLocaleString()} (>=2500 indica alta concentracion). Dependencia excesiva en pocos proveedores.`, + prioridad: ihh >= 5000 ? 'alta' : 'media', + detalle: '/alertas/concentracion-proveedores', + valor: ihh, + }; +} + +/** + * Riesgo cambiario: >10% de facturas en moneda != MXN + */ +async function alertaRiesgoCambiario(pool: Pool, contribuyenteId?: string | null): Promise { + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + const { rows: [r] } = await pool.query(` + SELECT + COUNT(*)::int as total, + COUNT(CASE WHEN moneda IS NOT NULL AND moneda != 'MXN' THEN 1 END)::int as no_mxn + FROM cfdis + WHERE ${VIGENTE} AND tipo_comprobante = 'I' + ${cf} + `); + + const total = r?.total || 0; + const noMxn = r?.no_mxn || 0; + if (total === 0 || noMxn === 0) return null; + + const porcentaje = Math.round((noMxn / total) * 10000) / 100; + if (porcentaje <= 10) return null; + + return { + id: 'riesgo-cambiario', + tipo: 'riesgo', + titulo: 'Riesgo Cambiario', + mensaje: `${porcentaje}% de las facturas (${noMxn} de ${total}) estan en moneda extranjera. Exposicion a fluctuaciones del tipo de cambio.`, + prioridad: porcentaje > 30 ? 'alta' : 'media', + valor: porcentaje, + }; +} + +/** + * Riesgo de cancelaciones: >10% de facturas canceladas en últimos 5 años + */ +async function alertaRiesgoCancelaciones(pool: Pool, contribuyenteId?: string | null): Promise { + const hace5 = new Date(); + hace5.setFullYear(hace5.getFullYear() - 5); + const fechaDesde = hace5.toISOString().split('T')[0]; + + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + + const { rows: [r] } = await pool.query(` + SELECT + COUNT(*)::int as total, + COUNT(CASE WHEN status IN ('Cancelado', '0') THEN 1 END)::int as cancelados + FROM cfdis + WHERE fecha_emision >= $1::date + ${cf} + `, [fechaDesde]); + + const total = r?.total || 0; + const cancelados = r?.cancelados || 0; + if (total === 0 || cancelados === 0) return null; + + const porcentaje = Math.round((cancelados / total) * 10000) / 100; + if (porcentaje <= 10) return null; + + return { + id: 'riesgo-cancelaciones', + tipo: 'riesgo', + titulo: 'Riesgo de Cancelaciones', + mensaje: `${porcentaje}% de las facturas (${cancelados} de ${total}) en los ultimos 5 años han sido canceladas.`, + prioridad: porcentaje > 25 ? 'alta' : 'media', + detalle: '/alertas/cancelaciones', + valor: porcentaje, + }; +} + +/** + * Gastos recibidos pagados en efectivo > $2,000 — Art. 27 fracción III LISR. + * No son deducibles. Surface el total + conteo para que el contador entienda + * el impacto y, si aplica, gestione cambio de forma de pago con el proveedor. + */ +async function alertaRiesgoTransaccional(pool: Pool, contribuyenteId?: string | null): Promise { + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + const { rows: [r] } = await pool.query(` + SELECT + COUNT(*)::int AS facturas, + COALESCE(SUM(COALESCE(total_mxn, 0)), 0)::numeric(14,2) AS monto + FROM cfdis + WHERE ${VIGENTE} + AND type = 'RECIBIDO' + AND tipo_comprobante = 'I' + AND metodo_pago = 'PUE' + AND forma_pago = '01' + AND COALESCE(total_mxn, 0) > 2000 + ${cf} + `); + + const facturas = r?.facturas || 0; + const monto = Number(r?.monto || 0); + if (facturas === 0) return null; + + return { + id: 'gastos-no-deducibles-efectivo', + tipo: 'riesgo', + titulo: 'Gastos no deducibles (efectivo > $2,000)', + mensaje: `${facturas} factura${facturas === 1 ? '' : 's'} recibida${facturas === 1 ? '' : 's'} por $${monto.toLocaleString('es-MX')} MXN se pagaron en efectivo (>$2,000). Art. 27 fracción III LISR las hace NO deducibles para ISR.`, + prioridad: monto > 50000 ? 'alta' : 'media', + detalle: '/impuestos', + valor: monto, + }; +} + +/** + * Estatus lista negra: si el RFC del tenant/contribuyente aparece en la lista negra + */ +async function alertaListaNegraPropia( + pool: Pool, + tenantId: string, + contribuyenteId?: string | null, +): Promise { + let rfc: string | undefined; + + if (contribuyenteId) { + const safeId = sanitizeUuid(contribuyenteId); + const { rows } = await pool.query( + 'SELECT rfc FROM contribuyentes WHERE entidad_id = $1', + [safeId], + ); + rfc = rows[0]?.rfc; + } else { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { rfc: true }, + }); + rfc = tenant?.rfc; + } + + if (!rfc) return null; + + const registro = await prisma.listaNegra.findUnique({ + where: { rfc }, + }); + if (!registro) return null; + + return { + id: 'lista-negra-propia', + tipo: 'lista-negra', + titulo: 'RFC en Lista Negra del SAT', + mensaje: `Tu RFC (${rfc}) aparece en la lista del Art. 69-B del CFF con situacion: ${registro.situacion}.`, + prioridad: 'alta', + }; +} + +/** + * Factura emitida a cliente en lista negra + */ +async function alertaClienteListaNegra(pool: Pool, contribuyenteId?: string | null): Promise { + // Fallback: consultar directo si dblink no funciona + const listaRfcs = await prisma.listaNegra.findMany({ + where: { situacion: { in: ['Definitivo', 'Presunto'] } }, + select: { rfc: true }, + }); + const rfcSet = new Set(listaRfcs.map(l => l.rfc)); + + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + + const { rows } = await pool.query(` + SELECT DISTINCT rfc_receptor as rfc + FROM cfdis + WHERE type = 'EMITIDO' AND ${VIGENTE} AND tipo_comprobante = 'I' + ${cf} + `); + + const clientesEnLista = rows.filter((r: any) => rfcSet.has(r.rfc)); + if (clientesEnLista.length === 0) return null; + + return { + id: 'lista-negra-clientes', + tipo: 'lista-negra', + titulo: 'Facturas Emitidas a Clientes en Lista Negra', + mensaje: `${clientesEnLista.length} cliente(s) a los que has facturado aparecen en la lista negra del SAT (Art. 69-B).`, + prioridad: 'alta', + detalle: '/alertas/lista-negra-clientes', + valor: clientesEnLista.length, + }; +} + +/** + * Factura recibida de proveedor en lista negra + */ +async function alertaProveedorListaNegra(pool: Pool, contribuyenteId?: string | null): Promise { + const listaRfcs = await prisma.listaNegra.findMany({ + where: { situacion: { in: ['Definitivo', 'Presunto'] } }, + select: { rfc: true }, + }); + const rfcSet = new Set(listaRfcs.map(l => l.rfc)); + + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + + const { rows } = await pool.query(` + SELECT DISTINCT rfc_emisor as rfc + FROM cfdis + WHERE type = 'RECIBIDO' AND ${VIGENTE} AND tipo_comprobante = 'I' + ${cf} + `); + + const proveedoresEnLista = rows.filter((r: any) => rfcSet.has(r.rfc)); + if (proveedoresEnLista.length === 0) return null; + + return { + id: 'lista-negra-proveedores', + tipo: 'lista-negra', + titulo: 'Facturas Recibidas de Proveedores en Lista Negra', + mensaje: `${proveedoresEnLista.length} proveedor(es) de los que has recibido facturas aparecen en la lista negra del SAT (Art. 69-B).`, + prioridad: 'alta', + detalle: '/alertas/lista-negra-proveedores', + valor: proveedoresEnLista.length, + }; +} + +/** + * Facturas de periodos anteriores canceladas este mes. + * Detecta CFDIs cuya fecha_cancelacion cae en el mes actual pero + * cuya fecha_emision es de un mes anterior. + */ +async function alertaCancelacionPeriodoAnterior(pool: Pool, contribuyenteId?: string | null): Promise { + const ahora = new Date(); + const inicioMes = `${ahora.getFullYear()}-${String(ahora.getMonth() + 1).padStart(2, '0')}-01`; + + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + + const { rows: [r] } = await pool.query(` + SELECT COUNT(*)::int as total, + COALESCE(SUM(COALESCE(total_mxn, 0)), 0) as monto + FROM cfdis + WHERE status IN ('Cancelado', '0') + AND fecha_cancelacion >= $1::date + AND fecha_emision < $1::date + ${cf} + `, [inicioMes]); + + const total = r?.total || 0; + if (total === 0) return null; + + const monto = Number(r.monto); + const montoFmt = monto.toLocaleString('es-MX', { style: 'currency', currency: 'MXN' }); + + return { + id: 'cancelacion-periodo-anterior', + tipo: 'cancelacion-retroactiva', + titulo: 'Facturas de Periodos Anteriores Canceladas', + mensaje: `${total} factura(s) emitida(s) en meses anteriores fueron canceladas este mes por un total de ${montoFmt}. Esto puede afectar declaraciones ya presentadas.`, + prioridad: 'alta', + detalle: '/alertas/cancelaciones-periodo-anterior', + valor: total, + }; +} + +/** + * CFDIs tipo E (Egreso / nota de crédito) con cfdi_tipo_relacion != '07' + * cuya(s) referencia(s) en cfdis_relacionados también aparecen en otro CFDI + * con cfdi_tipo_relacion = '07'. Señal de que el emisor debió usar 07 + * (aplicación de anticipo) pero puso 01/02/03/04: inflá gastos e IVA + * acreditable contra un anticipo ya consumido. + * + * La detección usa overlap de arrays (`&&`) sobre cfdis_relacionados + * pipe-separados — si X y Y comparten al menos un UUID referenciado y Y + * es 07, X es sospechoso. + */ +const SOSPECHOSA_TIPO_RELACION_WHERE = ` + c.tipo_comprobante = 'E' + AND c.status NOT IN ('Cancelado', '0') + AND c.cfdi_tipo_relacion IS NOT NULL + AND c.cfdi_tipo_relacion <> '07' + AND c.cfdis_relacionados IS NOT NULL + AND c.cfdis_relacionados <> '' + AND EXISTS ( + SELECT 1 FROM cfdis y + WHERE y.id <> c.id + AND y.cfdi_tipo_relacion = '07' + AND y.status NOT IN ('Cancelado', '0') + AND y.cfdis_relacionados IS NOT NULL + AND y.cfdis_relacionados <> '' + AND string_to_array(LOWER(y.cfdis_relacionados), '|') + && string_to_array(LOWER(c.cfdis_relacionados), '|') + ) + AND c.id NOT IN ( + SELECT cfdi_id FROM cfdi_descartados WHERE tipo_alerta = 'tipo-relacion-sospechosa' + ) +`; + +async function alertaTipoRelacionSospechosa( + pool: Pool, + contribuyenteId?: string | null, +): Promise { + const cf = contribuyenteId ? `AND c.contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + const { rows: [r] } = await pool.query(` + SELECT COUNT(*)::int AS total + FROM cfdis c + WHERE ${SOSPECHOSA_TIPO_RELACION_WHERE} + ${cf} + `); + + const total = r?.total || 0; + if (total === 0) return null; + + return { + id: 'tipo-relacion-sospechosa', + tipo: 'cfdi-inconsistente', + titulo: 'Nota de Crédito con Tipo de Relación sospechoso', + mensaje: `${total} CFDI(s) tipo E con TipoRelacion distinto de 07 referencian un CFDI tratado como anticipo por otra factura. Revisa si deberían haberse emitido como 07 (aplicación de anticipo).`, + prioridad: 'alta', + detalle: '/alertas/tipo-relacion-sospechosa', + valor: total, + }; +} + +/** Exportado para reutilizar en el controller de drill-down. */ +export const SOSPECHOSA_TIPO_RELACION_WHERE_EXPORT = SOSPECHOSA_TIPO_RELACION_WHERE; + +/** + * Tareas operativas próximas a vencer (≤3 días). Solo aplica cuando hay un + * contribuyente seleccionado — sin contribuyente, no se puede contar + * porque las tareas son siempre per-contribuyente. + */ +async function alertaTareasProximasVencer( + pool: Pool, + contribuyenteId?: string | null, +): Promise { + if (!contribuyenteId) return null; + const { total } = await contarTareasProximasVencer(pool, contribuyenteId); + if (total === 0) return null; + return { + id: 'tareas-proximas-vencer', + tipo: 'tareas', + titulo: 'Tareas próximas a vencer', + mensaje: `${total} tarea(s) operativa(s) vencen en los próximos 3 días.`, + prioridad: 'media', + detalle: '/configuracion/obligaciones', + valor: total, + }; +} + +/** + * RESICO PF cerca de salir del régimen por exceso de ingresos anuales. + * + * Art. 113-E LISR — el contribuyente PF en RESICO debe salir del régimen si + * sus ingresos del ejercicio exceden $3,500,000. **Importante:** el SAT + * considera ingresos acumulados de TODOS los regímenes del contribuyente, no + * solo los del 626. Por eso este query no filtra por `regimen_fiscal_emisor`. + * + * Umbrales: + * - $2,500,000 → alerta media (margen ~$1M) + * - $3,000,000 → alerta alta (margen ~$500k al límite) + * - $3,500,000 → alerta alta crítica ("ya superaste") + * + * Aplica solo cuando: + * 1. Hay un contribuyente seleccionado (per-tenant no tiene sentido — la + * alerta es por entidad fiscal individual) + * 2. RFC de 13 caracteres (Persona Física — RESICO PM no tiene este límite) + * 3. Régimen 626 está en su lista de regímenes activos + * + * Cálculo de ingresos: agregado de CFDIs emitidos vigentes del año en curso: + * + I PUE (cobradas al emitir) + * + P (complementos de pago — cobros de PPD anteriores) + * - E PUE (notas de crédito netan) + * + * Sin desglose por régimen ni filtro de conciliación. Se usa `total_mxn` como + * proxy de ingreso (incluye IVA — sobreestima ~16%, conservador para alerta). + */ +async function alertaResicoPfLimiteIngresos( + pool: Pool, + contribuyenteId?: string | null, +): Promise { + if (!contribuyenteId) return null; + + const safeId = sanitizeUuid(contribuyenteId); + + // Verificar elegibilidad: PF (RFC 13) + régimen 626 activo + const { rows: contribRows } = await pool.query( + `SELECT rfc, regimen_fiscal FROM contribuyentes WHERE entidad_id = $1`, + [safeId], + ); + const contrib = contribRows[0]; + if (!contrib) return null; + + const rfc: string = contrib.rfc || ''; + if (rfc.length !== 13) return null; // PM no aplica + + const regimenesCsv: string = contrib.regimen_fiscal || ''; + const regimenes = regimenesCsv.split(',').map((s: string) => s.trim()).filter(Boolean); + if (!regimenes.includes('626')) return null; + + // Suma ingresos del año en curso, agregado de TODOS los regímenes + const año = new Date().getFullYear(); + const { rows: [r] } = await pool.query(` + SELECT COALESCE(SUM( + CASE + WHEN tipo_comprobante = 'I' AND metodo_pago = 'PUE' THEN COALESCE(total_mxn, 0) + WHEN tipo_comprobante = 'P' THEN COALESCE(monto_pago_mxn, 0) + WHEN tipo_comprobante = 'E' AND metodo_pago = 'PUE' THEN -COALESCE(total_mxn, 0) + ELSE 0 + END + ), 0)::numeric AS ingresos + FROM cfdis + WHERE type = 'EMITIDO' + AND status NOT IN ('Cancelado', '0') + AND EXTRACT(YEAR FROM fecha_emision) = $1 + AND contribuyente_id = $2 + `, [año, safeId]); + + const ingresos = Number(r?.ingresos || 0); + const UMBRAL_AVISO = 2_500_000; + const UMBRAL_ALTO = 3_000_000; + const LIMITE_LEGAL = 3_500_000; // Art. 113-E LISR + + if (ingresos < UMBRAL_AVISO) return null; + + const ingresosFmt = ingresos.toLocaleString('es-MX', { + style: 'currency', currency: 'MXN', maximumFractionDigits: 0, + }); + + let prioridad: 'alta' | 'media' = 'media'; + let titulo = `RESICO PF cerca del límite anual`; + let mensaje = `Ingresos acumulados ${año} (todos los regímenes): ${ingresosFmt}. Límite RESICO PF: $3,500,000 (Art. 113-E LISR). Se considera ingresos de TODOS los regímenes, no solo del 626.`; + + if (ingresos >= LIMITE_LEGAL) { + prioridad = 'alta'; + titulo = `RESICO PF: límite anual EXCEDIDO`; + mensaje = `Ingresos acumulados ${año} (todos los regímenes): ${ingresosFmt}. Excede el límite de $3,500,000 del Art. 113-E LISR. El contribuyente debe salir de RESICO PF y tributar bajo régimen general (PF Empresarial).`; + } else if (ingresos >= UMBRAL_ALTO) { + prioridad = 'alta'; + titulo = `RESICO PF: cerca del límite ($3M+)`; + } + + return { + id: 'resico-pf-limite-ingresos', + tipo: 'limite-regimen', + titulo, + mensaje, + prioridad, + valor: ingresos, + }; +} + +/** + * Alerta si la última Opinión de Cumplimiento no es Positiva. + */ +async function alertaOpinionCumplimiento(pool: Pool, contribuyenteId?: string | null): Promise { + let rfcFilter = ''; + if (contribuyenteId) { + const safeId = sanitizeUuid(contribuyenteId); + const { rows: rfcRows } = await pool.query( + 'SELECT rfc FROM contribuyentes WHERE entidad_id = $1', + [safeId], + ); + const rfc: string | undefined = rfcRows[0]?.rfc; + if (rfc) rfcFilter = `WHERE rfc = '${rfc.replace(/'/g, "''")}'`; + } + + const { rows } = await pool.query(` + SELECT estatus, fecha_consulta + FROM opiniones_cumplimiento + ${rfcFilter} + ORDER BY fecha_consulta DESC + LIMIT 1 + `); + + if (rows.length === 0) return null; + + const { estatus, fecha_consulta } = rows[0]; + if (estatus === 'Positiva') return null; + + const fecha = new Date(fecha_consulta).toLocaleDateString('es-MX'); + + return { + id: 'opinion-cumplimiento-negativa', + tipo: 'opinion-cumplimiento', + titulo: `Opinión de Cumplimiento: ${estatus}`, + mensaje: `Tu Opinión de Cumplimiento ante el SAT es ${estatus}. Última consulta: ${fecha}. Revisa tus obligaciones fiscales.`, + prioridad: 'alta', + valor: 1, + }; +} + +/** + * Genera todas las alertas automáticas para un tenant. + */ +export async function generarAlertasAutomaticas( + pool: Pool, + tenantId: string, + contribuyenteId?: string | null, +): Promise { + const alertas = await Promise.all([ + alertaListaNegraPropia(pool, tenantId, contribuyenteId), + alertaClienteListaNegra(pool, contribuyenteId), + alertaProveedorListaNegra(pool, contribuyenteId), + alertaDiscrepanciaRegimen(pool, tenantId, contribuyenteId), + alertaConcentracionClientes(pool, contribuyenteId), + alertaConcentracionProveedores(pool, contribuyenteId), + alertaRiesgoCambiario(pool, contribuyenteId), + alertaRiesgoCancelaciones(pool, contribuyenteId), + alertaRiesgoTransaccional(pool, contribuyenteId), + alertaCancelacionPeriodoAnterior(pool, contribuyenteId), + alertaOpinionCumplimiento(pool, contribuyenteId), + alertaTipoRelacionSospechosa(pool, contribuyenteId), + alertaTareasProximasVencer(pool, contribuyenteId), + alertaResicoPfLimiteIngresos(pool, contribuyenteId), + ]); + + return alertas.filter((a): a is AlertaAuto => a !== null); +} + +/** + * Breakdown mensual de discrepancias de régimen de los últimos N meses. + * Cuenta facturas RECIBIDAS donde regimen_fiscal_receptor no coincide con + * los regímenes activos del tenant. Útil para el correo semanal — el cliente + * ve cuántas facturas con error le emitieron mes por mes. + */ +export async function getDiscrepanciasPorMes( + pool: Pool, + tenantId: string, + monthsBack = 6, + contribuyenteId?: string | null, +): Promise> { + const activos = await getRegimenesActivosClavesEfectivos(tenantId, pool, contribuyenteId); + if (activos.length === 0) return []; + + const desde = new Date(); + desde.setMonth(desde.getMonth() - (monthsBack - 1)); + desde.setDate(1); + const desdeStr = desde.toISOString().split('T')[0]; + + const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : ''; + + const { rows } = await pool.query(` + SELECT + EXTRACT(YEAR FROM fecha_emision)::int as año, + EXTRACT(MONTH FROM fecha_emision)::int as mes, + COUNT(*)::int as count + FROM cfdis + WHERE type = 'RECIBIDO' AND ${VIGENTE} + AND regimen_fiscal_receptor IS NOT NULL + AND regimen_fiscal_receptor != ALL($1) + AND fecha_emision >= $2::date + ${cf} + GROUP BY año, mes + ORDER BY año DESC, mes DESC + `, [activos, desdeStr]); + + const NOMBRES_MES = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; + + return rows.map((r: any) => ({ + año: r.año, + mes: r.mes, + count: r.count, + label: `${NOMBRES_MES[r.mes - 1]} ${r.año}`, + })); +} diff --git a/apps/api/src/services/alertas-manuales.service.ts b/apps/api/src/services/alertas-manuales.service.ts new file mode 100644 index 0000000..0353a5b --- /dev/null +++ b/apps/api/src/services/alertas-manuales.service.ts @@ -0,0 +1,299 @@ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; +import { generarEventosFiscales } from './calendario-fiscal.service.js'; +import { isDespachoTenant } from '@horux/shared'; + +interface AlertaManualGenerada { + tipo: string; + titulo: string; + mensaje: string; + prioridad: 'alta' | 'media'; + fechaVencimiento: string; +} + +// Mapeo de eventos del calendario a tipos de alerta (legacy Horux360) +const EVENTO_A_ALERTA: Record = { + 'Declaración mensual ISR': { prefijo: 'decl-isr', prioridad: 'alta' }, + 'Declaración mensual IVA': { prefijo: 'decl-iva', prioridad: 'alta' }, + 'Declaración mensual IEPS': { prefijo: 'decl-ieps', prioridad: 'media' }, + 'Pago provisional ISR': { prefijo: 'pago-isr', prioridad: 'alta' }, + 'Pago provisional IVA': { prefijo: 'pago-iva', prioridad: 'alta' }, + 'Pago provisional IEPS': { prefijo: 'pago-ieps', prioridad: 'media' }, + 'Declaración de sueldos y salarios': { prefijo: 'decl-sueldos', prioridad: 'media' }, + 'DIOT': { prefijo: 'diot', prioridad: 'media' }, + 'Contabilidad electrónica': { prefijo: 'contabilidad', prioridad: 'media' }, + 'Declaración anual PM': { prefijo: 'decl-anual-pm', prioridad: 'alta' }, + 'Declaración anual PF': { prefijo: 'decl-anual-pf', prioridad: 'alta' }, + 'Informativa Sueldos y Salarios': { prefijo: 'inf-sueldos', prioridad: 'media' }, +}; + +/** + * For despachos: generate alerts from the contribuyente's actual obligations + * (obligaciones_contribuyente) instead of the static fiscal calendar. + * Only generates alerts for obligations that the contribuyente actually has. + */ +async function sincronizarDesdeObligacionesContribuyente( + pool: Pool, + contribuyenteId: string, +): Promise<{ creadas: number; existentes: number }> { + const hoy = new Date(); + const currentPeriodo = hoy.toISOString().substring(0, 7); // "2026-04" + + // Get active obligations for this contribuyente + const { rows: obligaciones } = await pool.query(` + SELECT id, nombre, frecuencia, fecha_limite AS "fechaLimite", created_at AS "createdAt" + FROM obligaciones_contribuyente + WHERE contribuyente_id = $1 AND activa = true + `, [contribuyenteId]); + + // Get existing completions + const { rows: completions } = await pool.query(` + SELECT op.obligacion_id, op.periodo + FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.contribuyente_id = $1 AND op.completada = true + `, [contribuyenteId]); + const completionSet = new Set(completions.map(c => `${c.obligacion_id}:${c.periodo}`)); + + let creadas = 0; + let existentes = 0; + + for (const ob of obligaciones) { + const obStartPeriodo = ob.createdAt + ? new Date(ob.createdAt).toISOString().substring(0, 7) + : '2000-01'; + + // Check current and previous month + for (let offset = 0; offset <= 1; offset++) { + const d = new Date(hoy.getFullYear(), hoy.getMonth() - offset, 1); + const periodo = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; + + if (periodo < obStartPeriodo) continue; + if (!appliesToPeriod(ob.frecuencia, periodo)) continue; + if (completionSet.has(`${ob.id}:${periodo}`)) continue; + + // Generate alert type unique per obligation+period + const tipoUnico = `ob-${ob.id}-${periodo}`; + + const { rows: existing } = await pool.query( + `SELECT id FROM alertas WHERE tipo = $1`, + [tipoUnico], + ); + + if (existing.length > 0) { + existentes++; + continue; + } + + // Determine deadline (day 17 of next month for mensual) + const [y, m] = periodo.split('-').map(Number); + const nextMonth = m === 12 ? 1 : m + 1; + const nextYear = m === 12 ? y + 1 : y; + const fechaVencimiento = `${nextYear}-${String(nextMonth).padStart(2, '0')}-17`; + + const deadlineDate = new Date(fechaVencimiento + 'T23:59:59'); + const isPastDue = deadlineDate < hoy; + const prioridad = isPastDue ? 'alta' : 'media'; + const statusLabel = isPastDue ? 'Vencida' : 'Pendiente'; + + await pool.query(` + INSERT INTO alertas (tipo, titulo, mensaje, prioridad, fecha_vencimiento) + VALUES ($1, $2, $3, $4, $5) + `, [ + tipoUnico, + `${ob.nombre} - ${statusLabel}`, + `${ob.fechaLimite || 'Sin fecha límite especificada'}. Periodo: ${periodo}`, + prioridad, + fechaVencimiento, + ]); + creadas++; + } + } + + return { creadas, existentes }; +} + +function appliesToPeriod(frecuencia: string | null, periodo: string): boolean { + const [, month] = periodo.split('-').map(Number); + switch (frecuencia) { + case 'mensual': return true; + case 'bimestral': return month % 2 === 1; + case 'trimestral': return [1, 4, 7, 10].includes(month); + case 'anual': return month === 3 || month === 4; + case 'eventual': return false; + default: return true; + } +} + +/** + * Genera alertas manuales para eventos fiscales vencidos que no han sido resueltos. + * Para despachos: usa obligaciones per-contribuyente. + * Para Horux360: usa el calendario fiscal estático (legacy). + */ +export async function sincronizarAlertasManuales( + pool: Pool, + tenantId: string, + contribuyenteId?: string | null, +): Promise<{ creadas: number; existentes: number }> { + // Check if this is a despacho tenant + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { rfc: true, createdAt: true }, + }); + if (!tenant) return { creadas: 0, existentes: 0 }; + + // Despacho: use per-contribuyente obligations + if (isDespachoTenant(tenant.rfc)) { + if (contribuyenteId) { + return sincronizarDesdeObligacionesContribuyente(pool, contribuyenteId); + } + // "Todos los RFCs": don't generate new alerts — individual contribuyente alerts already exist + return { creadas: 0, existentes: 0 }; + } + + // Legacy Horux360: use static fiscal calendar + const hoy = new Date(); + const añoActual = hoy.getFullYear(); + const fechaCreacion = tenant.createdAt || hoy; + + const eventosActual = await generarEventosFiscales(tenantId, añoActual); + const eventosAnterior = await generarEventosFiscales(tenantId, añoActual - 1); + const todosEventos = [...eventosAnterior, ...eventosActual]; + + const vencidos = todosEventos.filter(e => { + const fecha = new Date(e.fechaLimite + 'T23:59:59'); + return fecha < hoy && fecha >= fechaCreacion; + }); + + let creadas = 0; + let existentes = 0; + + for (const evento of vencidos) { + const config = EVENTO_A_ALERTA[evento.titulo]; + if (!config) continue; + + const tipoUnico = `${config.prefijo}-${evento.fechaLimite}`; + + const { rows: existing } = await pool.query( + `SELECT id, resuelta FROM alertas WHERE tipo = $1`, + [tipoUnico] + ); + + if (existing.length > 0) { + existentes++; + continue; + } + + const esPago = evento.titulo.startsWith('Pago'); + const accion = esPago ? 'Pago pendiente' : 'No presentada'; + + await pool.query(` + INSERT INTO alertas (tipo, titulo, mensaje, prioridad, fecha_vencimiento) + VALUES ($1, $2, $3, $4, $5) + `, [ + tipoUnico, + `${evento.titulo} - ${accion}`, + `${evento.descripcion}. Fecha limite: ${new Date(evento.fechaLimite + 'T00:00:00').toLocaleDateString('es-MX', { day: 'numeric', month: 'long', year: 'numeric' })}`, + config.prioridad, + evento.fechaLimite, + ]); + + creadas++; + } + + return { creadas, existentes }; +} + +/** + * Obtiene alertas manuales pendientes (no resueltas). + * Filters by contribuyente or by user's accessible contribuyentes (for clientes). + */ +export async function getAlertasManualesPendientes( + pool: Pool, + contribuyenteId?: string | null, + userId?: string | null, + role?: string | null, +): Promise { + let contribuyenteFilter = ''; + const params: unknown[] = []; + + if (contribuyenteId) { + // Specific contribuyente selected + params.push(contribuyenteId); + contribuyenteFilter = `AND ( + tipo LIKE 'ob-%' AND SUBSTRING(tipo FROM 4 FOR 36) IN ( + SELECT id::text FROM obligaciones_contribuyente WHERE contribuyente_id = $${params.length} + ) + )`; + } else if (role === 'cliente' && userId) { + // Client with "Todos los RFCs" — only their accessible contribuyentes + params.push(userId); + contribuyenteFilter = `AND ( + tipo LIKE 'ob-%' AND SUBSTRING(tipo FROM 4 FOR 36) IN ( + SELECT id::text FROM obligaciones_contribuyente WHERE contribuyente_id IN ( + SELECT entidad_id FROM cliente_accesos WHERE user_id = $${params.length} + ) + ) + )`; + } else if (role === 'auxiliar' && userId) { + // Auxiliar: only their subcarteras' contribuyentes + params.push(userId); + contribuyenteFilter = `AND ( + tipo LIKE 'ob-%' AND SUBSTRING(tipo FROM 4 FOR 36) IN ( + SELECT id::text FROM obligaciones_contribuyente WHERE contribuyente_id IN ( + SELECT ce.entidad_id FROM cartera_entidades ce + JOIN carteras c ON c.id = ce.cartera_id + WHERE c.auxiliar_user_id = $${params.length} + UNION + SELECT ce.entidad_id FROM cartera_entidades ce + JOIN cartera_auxiliares ca ON ca.cartera_id = ce.cartera_id + WHERE ca.auxiliar_user_id = $${params.length} + ) + ) + )`; + } else if (role === 'supervisor' && userId) { + // Supervisor: only their carteras' contribuyentes + params.push(userId); + contribuyenteFilter = `AND ( + tipo LIKE 'ob-%' AND SUBSTRING(tipo FROM 4 FOR 36) IN ( + SELECT id::text FROM obligaciones_contribuyente WHERE contribuyente_id IN ( + SELECT ce.entidad_id FROM cartera_entidades ce + JOIN carteras c ON c.id = ce.cartera_id AND c.parent_id IS NULL + WHERE c.supervisor_user_id = $${params.length} + ) + ) + )`; + } + + // Exclude alerts for inactive obligations + const inactiveFilter = `AND NOT ( + tipo LIKE 'ob-%' AND SUBSTRING(tipo FROM 4 FOR 36) IN ( + SELECT id::text FROM obligaciones_contribuyente WHERE activa = false + ) + )`; + + const { rows } = await pool.query(` + SELECT id, tipo, titulo, mensaje, prioridad, + fecha_vencimiento as "fechaVencimiento", + leida, resuelta, created_at as "createdAt" + FROM alertas + WHERE resuelta = false + ${inactiveFilter} + ${contribuyenteFilter} + ORDER BY + CASE prioridad WHEN 'alta' THEN 1 WHEN 'media' THEN 2 ELSE 3 END, + fecha_vencimiento ASC + `, params); + + return rows; +} + +/** + * Marca una alerta como resuelta (presentada/pagada). + */ +export async function resolverAlerta(pool: Pool, id: string): Promise { + await pool.query( + `UPDATE alertas SET resuelta = true, leida = true WHERE id = $1`, + [id] + ); +} diff --git a/apps/api/src/services/alertas.service.ts b/apps/api/src/services/alertas.service.ts new file mode 100644 index 0000000..ff30f42 --- /dev/null +++ b/apps/api/src/services/alertas.service.ts @@ -0,0 +1,108 @@ +import type { Pool } from 'pg'; +import type { AlertaFull, AlertaCreate, AlertaUpdate, AlertasStats } from '@horux/shared'; + +export async function getAlertas( + pool: Pool, + filters: { leida?: boolean; resuelta?: boolean; prioridad?: string } +): Promise { + let whereClause = 'WHERE 1=1'; + const params: any[] = []; + let paramIndex = 1; + + if (filters.leida !== undefined) { + whereClause += ` AND leida = $${paramIndex++}`; + params.push(filters.leida); + } + if (filters.resuelta !== undefined) { + whereClause += ` AND resuelta = $${paramIndex++}`; + params.push(filters.resuelta); + } + if (filters.prioridad) { + whereClause += ` AND prioridad = $${paramIndex++}`; + params.push(filters.prioridad); + } + + const { rows } = await pool.query(` + SELECT id, tipo, titulo, mensaje, prioridad, + fecha_vencimiento as "fechaVencimiento", + leida, resuelta, created_at as "createdAt" + FROM alertas + ${whereClause} + ORDER BY + CASE prioridad WHEN 'alta' THEN 1 WHEN 'media' THEN 2 ELSE 3 END, + created_at DESC + `, params); + + return rows; +} + +export async function getAlertaById(pool: Pool, id: number): Promise { + const { rows } = await pool.query(` + SELECT id, tipo, titulo, mensaje, prioridad, + fecha_vencimiento as "fechaVencimiento", + leida, resuelta, created_at as "createdAt" + FROM alertas + WHERE id = $1 + `, [id]); + return rows[0] || null; +} + +export async function createAlerta(pool: Pool, data: AlertaCreate): Promise { + const { rows } = await pool.query(` + INSERT INTO alertas (tipo, titulo, mensaje, prioridad, fecha_vencimiento) + VALUES ($1, $2, $3, $4, $5) + RETURNING id, tipo, titulo, mensaje, prioridad, + fecha_vencimiento as "fechaVencimiento", + leida, resuelta, created_at as "createdAt" + `, [data.tipo, data.titulo, data.mensaje, data.prioridad, data.fechaVencimiento || null]); + return rows[0]; +} + +export async function updateAlerta(pool: Pool, id: number, data: AlertaUpdate): Promise { + const sets: string[] = []; + const params: any[] = []; + let paramIndex = 1; + + if (data.leida !== undefined) { + sets.push(`leida = $${paramIndex++}`); + params.push(data.leida); + } + if (data.resuelta !== undefined) { + sets.push(`resuelta = $${paramIndex++}`); + params.push(data.resuelta); + } + + params.push(id); + + const { rows } = await pool.query(` + UPDATE alertas + SET ${sets.join(', ')} + WHERE id = $${paramIndex} + RETURNING id, tipo, titulo, mensaje, prioridad, + fecha_vencimiento as "fechaVencimiento", + leida, resuelta, created_at as "createdAt" + `, params); + + return rows[0]; +} + +export async function deleteAlerta(pool: Pool, id: number): Promise { + await pool.query(`DELETE FROM alertas WHERE id = $1`, [id]); +} + +export async function getStats(pool: Pool): Promise { + const { rows: [stats] } = await pool.query(` + SELECT + COUNT(*)::int as total, + COUNT(CASE WHEN leida = false THEN 1 END)::int as "noLeidas", + COUNT(CASE WHEN prioridad = 'alta' AND resuelta = false THEN 1 END)::int as alta, + COUNT(CASE WHEN prioridad = 'media' AND resuelta = false THEN 1 END)::int as media, + COUNT(CASE WHEN prioridad = 'baja' AND resuelta = false THEN 1 END)::int as baja + FROM alertas + `); + return stats; +} + +export async function markAllAsRead(pool: Pool): Promise { + await pool.query(`UPDATE alertas SET leida = true WHERE leida = false`); +} diff --git a/apps/api/src/services/auth.service.ts b/apps/api/src/services/auth.service.ts new file mode 100644 index 0000000..b69dbc2 --- /dev/null +++ b/apps/api/src/services/auth.service.ts @@ -0,0 +1,653 @@ +import { prisma, tenantDb } from '../config/database.js'; +import { hashPassword, verifyPassword } from '../auth/passwords.js'; +import { generateAccessToken, generateRefreshToken, verifyToken } from '../auth/tokens.js'; +import { AppError } from '../middlewares/error.middleware.js'; +import { auditLog } from '../utils/audit.js'; +import { getPlatformRoles } from '../utils/platform-admin.js'; +import { getUserTenants, verifyMembership } from '../utils/memberships.js'; +import { emailService } from './email/email.service.js'; +import { env } from '../config/env.js'; +import { invalidateTokenVersionCache } from '../middlewares/auth.middleware.js'; +import type { LoginRequest, RegisterRequest, LoginResponse, Role } from '@horux/shared'; +import { randomBytes } from 'crypto'; + +export async function register(data: RegisterRequest): Promise { + const existingUser = await prisma.user.findUnique({ + where: { email: data.usuario.email.toLowerCase() }, + }); + + if (existingUser) { + throw new AppError(400, 'El email ya está registrado'); + } + + const existingTenant = await prisma.tenant.findUnique({ + where: { rfc: data.empresa.rfc }, + }); + + if (existingTenant) { + throw new AppError(400, 'El RFC ya está registrado'); + } + + // Provision a dedicated database for this tenant + const databaseName = await tenantDb.provisionDatabase(data.empresa.rfc); + + const tenant = await prisma.tenant.create({ + data: { + nombre: data.empresa.nombre, + rfc: data.empresa.rfc.toUpperCase(), + plan: 'trial', + databaseName, + }, + }); + + const passwordHash = await hashPassword(data.usuario.password); + const adminRol = await prisma.rol.findUnique({ where: { nombre: 'owner' } }); + if (!adminRol) throw new AppError(500, 'Rol admin no encontrado en catálogo'); + + const user = await prisma.user.create({ + data: { + email: data.usuario.email.toLowerCase(), + passwordHash, + nombre: data.usuario.nombre, + lastTenantId: tenant.id, + }, + }); + + // Crea membership owner del caller en el tenant recién creado (fase 4 multi-tenant) + await prisma.tenantMembership.create({ + data: { + userId: user.id, + tenantId: tenant.id, + rolId: adminRol.id, + isOwner: true, + active: true, + }, + }); + + const ownerRole: Role = 'owner'; + const tokenPayload = { + userId: user.id, + email: user.email, + role: ownerRole, + tenantId: tenant.id, + }; + + const accessToken = generateAccessToken(tokenPayload); + const refreshToken = generateRefreshToken(tokenPayload); + + await prisma.refreshToken.create({ + data: { + userId: user.id, + token: refreshToken, + expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + }, + }); + + return { + accessToken, + refreshToken, + user: { + id: user.id, + email: user.email, + nombre: user.nombre, + role: ownerRole, + tenantId: tenant.id, + tenantName: tenant.nombre, + tenantRfc: tenant.rfc, + plan: tenant.plan, + }, + }; +} + +export async function login(data: LoginRequest): Promise { + const user = await prisma.user.findUnique({ + where: { email: data.email.toLowerCase() }, + }); + + if (!user) { + throw new AppError(401, 'Credenciales inválidas'); + } + + if (!user.active) { + throw new AppError(401, 'Usuario desactivado'); + } + + const isValidPassword = await verifyPassword(data.password, user.passwordHash); + + if (!isValidPassword) { + throw new AppError(401, 'Credenciales inválidas'); + } + + // Resuelve el tenant activo desde memberships. Prefiere `lastTenantId` si + // existe Y el user tiene membership activa ahí; sino cae al primer membership + // por joinedAt ASC. + const allMemberships = await prisma.tenantMembership.findMany({ + where: { userId: user.id, active: true, tenant: { active: true } }, + include: { tenant: true, rol: true }, + orderBy: { joinedAt: 'asc' }, + }); + + let activeTenant; + let activeRole: Role; + + if (allMemberships.length === 0) { + // Edge case: user sin membership activa. Si tiene platformRoles (admin + // global), permite login con cualquier tenant activo como nominal — su + // trabajo real es vía impersonación desde /clientes. Sin esto, no podría + // ni entrar a la plataforma para crear el primer cliente. + const earlyPlatformRoles = await getPlatformRoles(user.id); + if (earlyPlatformRoles.length === 0) { + throw new AppError(401, 'No tienes acceso a ninguna empresa activa'); + } + const fallbackTenant = await prisma.tenant.findFirst({ + where: { active: true }, + orderBy: { createdAt: 'asc' }, + }); + if (!fallbackTenant) { + throw new AppError(503, 'No hay tenants activos en el sistema. Ejecuta `pnpm db:seed` para bootstrap.'); + } + activeTenant = fallbackTenant; + activeRole = 'visor' as Role; // mínimo — la autorización real viene de platformRoles + } else { + const preferred = user.lastTenantId + ? allMemberships.find(m => m.tenantId === user.lastTenantId) + : null; + const activeMembership = preferred ?? allMemberships[0]; + activeTenant = activeMembership.tenant; + activeRole = activeMembership.rol.nombre as Role; + } + + // `loginCount` se incrementa SOLO en login (NO en refresh) — es la métrica + // que dispara el auto-dismiss del onboarding tras N sesiones. + const updatedUser = await prisma.user.update({ + where: { id: user.id }, + data: { + lastLogin: new Date(), + lastTenantId: activeTenant.id, + loginCount: { increment: 1 }, + }, + select: { loginCount: true, onboardingDismissedAt: true }, + }); + + auditLog({ + userId: user.id, + tenantId: activeTenant.id, + action: 'user.login', + metadata: { email: user.email, tenantRfc: activeTenant.rfc }, + }); + + const [platformRoles, tenants] = await Promise.all([ + getPlatformRoles(user.id), + getUserTenants(user.id), + ]); + + const tokenPayload = { + userId: user.id, + email: user.email, + role: activeRole, + tenantId: activeTenant.id, + platformRoles, + tokenVersion: user.tokenVersion, + }; + + const accessToken = generateAccessToken(tokenPayload); + const refreshToken = generateRefreshToken(tokenPayload); + + await prisma.refreshToken.create({ + data: { + userId: user.id, + token: refreshToken, + expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + }, + }); + + return { + accessToken, + refreshToken, + user: { + id: user.id, + email: user.email, + nombre: user.nombre, + role: activeRole, + tenantId: activeTenant.id, + tenantName: activeTenant.nombre, + tenantRfc: activeTenant.rfc, + plan: activeTenant.plan, + platformRoles, + tenants, + loginCount: updatedUser.loginCount, + onboardingDismissedAt: updatedUser.onboardingDismissedAt, + }, + }; +} + +export async function refreshTokens(token: string): Promise<{ accessToken: string; refreshToken: string }> { + // Use a transaction to prevent race conditions + return await prisma.$transaction(async (tx) => { + const storedToken = await tx.refreshToken.findUnique({ + where: { token }, + }); + + if (!storedToken) { + throw new AppError(401, 'Token inválido'); + } + + if (storedToken.expiresAt < new Date()) { + await tx.refreshToken.deleteMany({ where: { id: storedToken.id } }); + throw new AppError(401, 'Token expirado'); + } + + const payload = verifyToken(token); + + const user = await tx.user.findUnique({ + where: { id: payload.userId }, + }); + + if (!user || !user.active) { + throw new AppError(401, 'Usuario no encontrado o desactivado'); + } + + // Re-valida que el user sigue teniendo membership activa en el tenant del + // JWT. Si lo removieron de ahí, cae al primer membership disponible. + const currentMembership = await tx.tenantMembership.findFirst({ + where: { userId: user.id, tenantId: payload.tenantId, active: true, tenant: { active: true } }, + include: { tenant: true, rol: true }, + }); + let activeMembership = currentMembership; + if (!activeMembership) { + activeMembership = await tx.tenantMembership.findFirst({ + where: { userId: user.id, active: true, tenant: { active: true } }, + include: { tenant: true, rol: true }, + orderBy: { joinedAt: 'asc' }, + }); + } + if (!activeMembership) { + throw new AppError(401, 'No tienes acceso a ninguna empresa activa'); + } + + // Use deleteMany to avoid error if already deleted (race condition) + await tx.refreshToken.deleteMany({ where: { id: storedToken.id } }); + + const platformRoles = await getPlatformRoles(user.id); + + const newTokenPayload = { + userId: user.id, + email: user.email, + role: activeMembership.rol.nombre as Role, + tenantId: activeMembership.tenantId, + platformRoles, + tokenVersion: user.tokenVersion, + }; + + const accessToken = generateAccessToken(newTokenPayload); + const refreshToken = generateRefreshToken(newTokenPayload); + + await tx.refreshToken.create({ + data: { + userId: user.id, + token: refreshToken, + expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + }, + }); + + return { accessToken, refreshToken }; + }); +} + +export async function logout(token: string): Promise { + // Busca el refreshToken antes de borrarlo para capturar el userId en auditoría + const rt = await prisma.refreshToken.findFirst({ + where: { token }, + select: { userId: true }, + }); + + await prisma.refreshToken.deleteMany({ + where: { token }, + }); + + if (rt) { + const tenantId = (await prisma.user.findUnique({ + where: { id: rt.userId }, + select: { lastTenantId: true }, + }))?.lastTenantId ?? undefined; + + auditLog({ + userId: rt.userId, + tenantId, + action: 'user.logout', + }); + } +} + +// ============================================================================ +// Password reset +// ============================================================================ + +const PASSWORD_RESET_EXPIRY_MS = 60 * 60 * 1000; // 1 hora + +/** + * Solicita recuperación de contraseña. No revela si el email existe (anti-enumeration). + * + * Si el email es válido y user activo: + * - Invalida cualquier token previo no usado del mismo user + * - Genera token criptográficamente seguro (32 bytes hex) + * - Envía email con link de reset (expira en 1h) + * + * Rate limit: aplicar en la capa de rutas (3/hora por IP). + */ +export async function requestPasswordReset(email: string): Promise { + const normalizedEmail = email.trim().toLowerCase(); + const user = await prisma.user.findUnique({ + where: { email: normalizedEmail }, + select: { id: true, email: true, nombre: true, active: true, lastTenantId: true }, + }); + + // Respuesta idéntica para email existente/no-existente (anti-enumeration) + if (!user || !user.active) { + console.log(`[PasswordReset] Request para email inexistente o inactivo: ${normalizedEmail}`); + return; + } + + // Invalida tokens previos no usados (marca como usados) + await prisma.passwordResetToken.updateMany({ + where: { userId: user.id, usedAt: null }, + data: { usedAt: new Date() }, + }); + + const token = randomBytes(32).toString('hex'); + const expiresAt = new Date(Date.now() + PASSWORD_RESET_EXPIRY_MS); + + await prisma.passwordResetToken.create({ + data: { userId: user.id, token, expiresAt }, + }); + + const resetUrl = `${env.FRONTEND_URL}/reset-password?token=${token}`; + emailService.sendPasswordReset(user.email, { nombre: user.nombre, resetUrl }) + .catch(err => console.error('[EMAIL] Password reset notification failed:', err)); + + auditLog({ + userId: user.id, + tenantId: user.lastTenantId ?? undefined, + action: 'user.password_reset_requested', + metadata: { email: user.email }, + }); + + console.log(`[PasswordReset] Token emitido para ${normalizedEmail}, expira ${expiresAt.toISOString()}`); +} + +/** + * Confirma recuperación de contraseña con token válido + nueva contraseña. + * + * Validaciones: + * - Password mínimo 8 caracteres + * - Token existe, no usado, no expirado + * + * Al éxito: + * - Actualiza password hash + * - Marca token como usado (single-use) + * - Borra todos los refresh tokens del user (invalida sesiones activas → re-login forzado) + */ +export async function confirmPasswordReset(token: string, newPassword: string): Promise { + if (!newPassword || newPassword.length < 8) { + throw new AppError(400, 'La contraseña debe tener al menos 8 caracteres'); + } + + const record = await prisma.passwordResetToken.findUnique({ + where: { token }, + select: { id: true, userId: true, usedAt: true, expiresAt: true }, + }); + + if (!record) throw new AppError(400, 'Token inválido'); + if (record.usedAt) throw new AppError(400, 'Este enlace ya fue usado. Solicita uno nuevo.'); + if (record.expiresAt < new Date()) throw new AppError(400, 'El enlace expiró. Solicita uno nuevo.'); + + const passwordHash = await hashPassword(newPassword); + + await prisma.$transaction([ + // Actualiza hash + incrementa tokenVersion (invalida access tokens vivos) + prisma.user.update({ + where: { id: record.userId }, + data: { passwordHash, tokenVersion: { increment: 1 } }, + }), + prisma.passwordResetToken.update({ + where: { id: record.id }, + data: { usedAt: new Date() }, + }), + // Invalida todos los refresh tokens activos del user + prisma.refreshToken.deleteMany({ + where: { userId: record.userId }, + }), + ]); + + // Propaga el incremento al cache del middleware (en todos los PM2 workers) + invalidateTokenVersionCache(record.userId); + + const user = await prisma.user.findUnique({ + where: { id: record.userId }, + select: { lastTenantId: true, email: true }, + }); + + auditLog({ + userId: record.userId, + tenantId: user?.lastTenantId ?? undefined, + action: 'user.password_reset_completed', + metadata: { email: user?.email }, + }); + + console.log(`[PasswordReset] Completado para user ${record.userId}`); +} + +// ============================================================================ +// Change password (authenticated) + Logout all +// ============================================================================ + +/** + * Cambia la contraseña de un user autenticado. Requiere password actual para + * prevenir cambios por alguien con acceso temporal a la sesión (ej: laptop + * compartida dejada abierta). Incrementa tokenVersion — invalida TODAS las + * sesiones activas del user (forza re-login en otros dispositivos). + */ +export async function changePassword(params: { + userId: string; + currentPassword: string; + newPassword: string; +}): Promise { + if (params.newPassword.length < 8) { + throw new AppError(400, 'La contraseña debe tener al menos 8 caracteres'); + } + if (params.currentPassword === params.newPassword) { + throw new AppError(400, 'La nueva contraseña debe ser distinta a la actual'); + } + + const user = await prisma.user.findUnique({ + where: { id: params.userId }, + select: { id: true, passwordHash: true, email: true, lastTenantId: true, active: true }, + }); + if (!user || !user.active) throw new AppError(401, 'Usuario no encontrado'); + + const validCurrent = await verifyPassword(params.currentPassword, user.passwordHash); + if (!validCurrent) throw new AppError(401, 'Contraseña actual incorrecta'); + + const newHash = await hashPassword(params.newPassword); + + await prisma.$transaction([ + prisma.user.update({ + where: { id: user.id }, + data: { passwordHash: newHash, tokenVersion: { increment: 1 } }, + }), + prisma.refreshToken.deleteMany({ where: { userId: user.id } }), + ]); + + invalidateTokenVersionCache(user.id); + + auditLog({ + userId: user.id, + tenantId: user.lastTenantId ?? undefined, + action: 'user.password_changed', + metadata: { email: user.email }, + }); + + console.log(`[ChangePassword] Completado para user ${user.id}`); +} + +/** + * Cierra todas las sesiones activas del user. Usado por el botón + * "Cerrar todas las sesiones" en /configuracion/seguridad. Incrementa + * tokenVersion + borra refresh tokens. El user se queda sin acceso y debe + * re-loguearse (incluyendo la sesión actual, por diseño — es lo que el + * usuario pidió explícitamente). + */ +export async function logoutAllSessions(userId: string): Promise { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { lastTenantId: true, email: true }, + }); + if (!user) throw new AppError(404, 'Usuario no encontrado'); + + await prisma.$transaction([ + prisma.user.update({ + where: { id: userId }, + data: { tokenVersion: { increment: 1 } }, + }), + prisma.refreshToken.deleteMany({ where: { userId } }), + ]); + + invalidateTokenVersionCache(userId); + + auditLog({ + userId, + tenantId: user.lastTenantId ?? undefined, + action: 'user.sessions_invalidated', + metadata: { email: user.email, reason: 'logout_all' }, + }); + + console.log(`[LogoutAll] Sesiones invalidadas para user ${userId}`); +} + +// ============================================================================ +// Onboarding dismiss +// ============================================================================ + +/** + * Marca el onboarding como dismissed. Idempotente — si ya estaba seteado, no + * sobrescribe el timestamp original (preserva la fecha del primer dismiss). + */ +export async function dismissOnboarding(userId: string): Promise<{ onboardingDismissedAt: Date }> { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { onboardingDismissedAt: true }, + }); + if (!user) throw new AppError(404, 'Usuario no encontrado'); + + if (user.onboardingDismissedAt) { + return { onboardingDismissedAt: user.onboardingDismissedAt }; + } + + const updated = await prisma.user.update({ + where: { id: userId }, + data: { onboardingDismissedAt: new Date() }, + select: { onboardingDismissedAt: true }, + }); + return { onboardingDismissedAt: updated.onboardingDismissedAt! }; +} + +// ============================================================================ +// Switch tenant (multi-membership) +// ============================================================================ + +/** + * Cambia el tenant activo del user. Valida que tenga membership activa en el + * tenant destino, luego emite un nuevo par de tokens apuntando a ese tenantId + * (con el rol que tiene en ese tenant específico). El refresh token actual se + * invalida — el user opera con el par nuevo desde este momento. + * + * Casos de uso: + * - Owner con varias empresas cambia entre ellas + * - Contador que atiende múltiples clientes cambia de empresa activa + * + * Un user con 1 sola membership no debería llamarlo (no cambia nada), pero si + * lo hace funciona igual: le da tokens nuevos apuntando al mismo tenant. + */ +export async function switchTenant(params: { + userId: string; + currentRefreshToken: string; + targetTenantId: string; +}): Promise { + const membership = await verifyMembership(params.userId, params.targetTenantId); + if (!membership) { + throw new AppError(403, 'No tienes acceso a esa empresa'); + } + + const user = await prisma.user.findUnique({ + where: { id: params.userId }, + }); + if (!user || !user.active) throw new AppError(401, 'Usuario no encontrado'); + + const targetTenant = await prisma.tenant.findUnique({ + where: { id: params.targetTenantId }, + }); + if (!targetTenant || !targetTenant.active) { + throw new AppError(404, 'Empresa no encontrada o desactivada'); + } + + // Persiste el target como "último tenant activo" — al re-loguear caerá aquí + // sin tener que volver a hacer switch. + const previousTenantId = user.lastTenantId; + await prisma.user.update({ + where: { id: user.id }, + data: { lastTenantId: targetTenant.id }, + }); + + // Invalida el refresh token actual (puede no existir si el caller pasó el + // access token por error — deleteMany es idempotente). + await prisma.refreshToken.deleteMany({ where: { token: params.currentRefreshToken } }); + + const [platformRoles, tenants] = await Promise.all([ + getPlatformRoles(user.id), + getUserTenants(user.id), + ]); + + const tokenPayload = { + userId: user.id, + email: user.email, + role: membership.rolNombre, + tenantId: targetTenant.id, + platformRoles, + tokenVersion: user.tokenVersion, + }; + + const accessToken = generateAccessToken(tokenPayload); + const refreshToken = generateRefreshToken(tokenPayload); + + await prisma.refreshToken.create({ + data: { + userId: user.id, + token: refreshToken, + expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + }, + }); + + auditLog({ + userId: user.id, + tenantId: targetTenant.id, + action: 'user.tenant_switched', + metadata: { from: previousTenantId ?? null, to: targetTenant.id, targetRfc: targetTenant.rfc }, + }); + + return { + accessToken, + refreshToken, + user: { + id: user.id, + email: user.email, + nombre: user.nombre, + role: membership.rolNombre, + tenantId: targetTenant.id, + tenantName: targetTenant.nombre, + tenantRfc: targetTenant.rfc, + plan: targetTenant.plan, + platformRoles, + tenants, + }, + }; +} diff --git a/apps/api/src/services/bancos.service.ts b/apps/api/src/services/bancos.service.ts new file mode 100644 index 0000000..0a60b2c --- /dev/null +++ b/apps/api/src/services/bancos.service.ts @@ -0,0 +1,63 @@ +import type { Pool } from 'pg'; + +export interface Banco { + id: number; + banco: string; + terminacionCuenta: string; + creadoEn: string; +} + +export async function getBancos(pool: Pool, contribuyenteId?: string | null): Promise { + const conditions = []; + const params: unknown[] = []; + if (contribuyenteId) { + params.push(contribuyenteId); + conditions.push(`contribuyente_id = $${params.length}`); + } + const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + const { rows } = await pool.query(` + SELECT id, banco, terminacion_cuenta as "terminacionCuenta", + creado_en as "creadoEn" + FROM bancos ${where} ORDER BY banco + `, params); + return rows; +} + +export async function createBanco(pool: Pool, data: { banco: string; terminacionCuenta: string; contribuyenteId?: string }): Promise { + const { rows } = await pool.query(` + INSERT INTO bancos (banco, terminacion_cuenta, contribuyente_id) + VALUES ($1, $2, $3) + RETURNING id, banco, terminacion_cuenta as "terminacionCuenta", creado_en as "creadoEn" + `, [data.banco, data.terminacionCuenta, data.contribuyenteId || null]); + return rows[0]; +} + +export async function updateBanco(pool: Pool, id: number, data: { banco?: string; terminacionCuenta?: string }): Promise { + const fields: string[] = []; + const params: any[] = []; + let idx = 1; + + if (data.banco) { fields.push(`banco = $${idx++}`); params.push(data.banco); } + if (data.terminacionCuenta) { fields.push(`terminacion_cuenta = $${idx++}`); params.push(data.terminacionCuenta); } + + if (fields.length === 0) throw new Error('Nada que actualizar'); + + params.push(id); + const { rows } = await pool.query(` + UPDATE bancos SET ${fields.join(', ')} WHERE id = $${idx} + RETURNING id, banco, terminacion_cuenta as "terminacionCuenta", creado_en as "creadoEn" + `, params); + + if (rows.length === 0) throw new Error('Banco no encontrado'); + return rows[0]; +} + +export async function deleteBanco(pool: Pool, id: number): Promise { + const { rows } = await pool.query( + `SELECT COUNT(*)::int as count FROM conciliaciones WHERE id_banco = $1`, [id] + ); + if (rows[0].count > 0) { + throw new Error('No se puede eliminar un banco con conciliaciones asociadas'); + } + await pool.query(`DELETE FROM bancos WHERE id = $1`, [id]); +} diff --git a/apps/api/src/services/calendario-fiscal.service.ts b/apps/api/src/services/calendario-fiscal.service.ts new file mode 100644 index 0000000..326eb2c --- /dev/null +++ b/apps/api/src/services/calendario-fiscal.service.ts @@ -0,0 +1,271 @@ +import { prisma } from '../config/database.js'; +import { getRegimenesActivosClaves } from './regimen.service.js'; +import { getObligaciones } from './obligaciones.service.js'; +import type { Pool } from 'pg'; + +interface EventoGenerado { + titulo: string; + tipo: string; + fechaLimite: string; + recurrencia: string; + completado: boolean; + descripcion: string; +} + +/** + * Obtener días inhábiles del año como Set de strings 'YYYY-MM-DD' + */ +async function getDiasInhabiles(año: number): Promise> { + const rows = await prisma.diaInhabil.findMany({ + where: { + fecha: { + gte: new Date(`${año}-01-01`), + lte: new Date(`${año}-12-31`), + }, + }, + }); + return new Set(rows.map(r => r.fecha.toISOString().split('T')[0])); +} + +/** + * Si la fecha cae en día inhábil (sábado, domingo, festivo), recorrer al siguiente día hábil + */ +function siguienteDiaHabil(fecha: Date, inhabiles: Set): Date { + const d = new Date(fecha); + while (true) { + const dow = d.getDay(); + const str = d.toISOString().split('T')[0]; + if (dow !== 0 && dow !== 6 && !inhabiles.has(str)) { + return d; + } + d.setDate(d.getDate() + 1); + } +} + +/** + * Agregar N días hábiles a una fecha + */ +function agregarDiasHabiles(fecha: Date, dias: number, inhabiles: Set): Date { + const d = new Date(fecha); + let added = 0; + while (added < dias) { + d.setDate(d.getDate() + 1); + const dow = d.getDay(); + const str = d.toISOString().split('T')[0]; + if (dow !== 0 && dow !== 6 && !inhabiles.has(str)) { + added++; + } + } + return d; +} + +/** + * Calcula días adicionales por RFC según Resolución Miscelánea Fiscal + * Sexto dígito numérico del RFC: + * 1-2: +1 día, 3-4: +2, 5-6: +3, 7-8: +4, 9-0: +5 + */ +function diasExtensionRfc(rfc: string): number { + // Extraer sexto dígito numérico + const numeros = rfc.replace(/[^0-9]/g, ''); + if (numeros.length < 6) return 0; + const sexto = parseInt(numeros[5]); + + if (sexto === 1 || sexto === 2) return 1; + if (sexto === 3 || sexto === 4) return 2; + if (sexto === 5 || sexto === 6) return 3; + if (sexto === 7 || sexto === 8) return 4; + return 5; // 9 o 0 +} + +/** + * Genera los eventos fiscales para un tenant en un año dado, + * basándose en el catálogo central y las reglas del SAT. + */ +export async function generarEventosFiscales( + tenantId: string, + año: number, +): Promise { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { rfc: true }, + }); + if (!tenant) return []; + + const rfc = tenant.rfc; + const inhabiles = await getDiasInhabiles(año); + + // Regímenes activos del tenant + const activos = await getRegimenesActivosClaves(tenantId); + const activosSet = new Set(activos); + + const catalogo = await prisma.eventoFiscalCatalogo.findMany({ + where: { activo: true }, + }); + + const eventos: EventoGenerado[] = []; + const hoy = new Date(); + + for (const cat of catalogo) { + // Filtrar por régimen: si el evento es para regímenes específicos, + // verificar que el tenant tenga al menos uno de ellos activo + if (cat.regimenes !== 'todos' && activos.length > 0) { + const regimenesEvento = cat.regimenes.split(',').map(r => r.trim()); + const aplica = regimenesEvento.some(r => activosSet.has(r)); + if (!aplica) continue; + } + + if (cat.recurrencia === 'mensual') { + for (let mes = 1; mes <= 12; mes++) { + // Mes relativo: 1 = mes posterior al que se declara + const mesObligacion = mes; // mes que se declara + const mesVencimiento = mes + cat.mesRelativo; + const añoVencimiento = mesVencimiento > 12 ? año + 1 : año; + const mesReal = mesVencimiento > 12 ? mesVencimiento - 12 : mesVencimiento; + + // Fecha base + const lastDay = new Date(añoVencimiento, mesReal, 0).getDate(); + const dia = Math.min(cat.diaBase, lastDay); + let fechaLimite = new Date(añoVencimiento, mesReal - 1, dia); + + // Ajustar a día hábil + fechaLimite = siguienteDiaHabil(fechaLimite, inhabiles); + + // Extensión por RFC + if (cat.usaExtensionRfc) { + const diasExtra = diasExtensionRfc(rfc); + fechaLimite = agregarDiasHabiles(fechaLimite, diasExtra, inhabiles); + } + + const completado = fechaLimite < hoy; + + const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']; + + eventos.push({ + titulo: cat.titulo, + tipo: cat.tipo, + fechaLimite: fechaLimite.toISOString().split('T')[0], + recurrencia: cat.recurrencia, + completado, + descripcion: `${cat.titulo} — ${meses[mesObligacion - 1]} ${año}`, + }); + } + } else if (cat.recurrencia === 'anual' && cat.mesFijo) { + const lastDay = new Date(año, cat.mesFijo, 0).getDate(); + const dia = Math.min(cat.diaBase, lastDay); + let fechaLimite = new Date(año, cat.mesFijo - 1, dia); + + fechaLimite = siguienteDiaHabil(fechaLimite, inhabiles); + + eventos.push({ + titulo: cat.titulo, + tipo: cat.tipo, + fechaLimite: fechaLimite.toISOString().split('T')[0], + recurrencia: cat.recurrencia, + completado: fechaLimite < hoy, + descripcion: `${cat.titulo} — Ejercicio ${año - 1}`, + }); + } + } + + // Ordenar por fecha + eventos.sort((a, b) => a.fechaLimite.localeCompare(b.fechaLimite)); + + return eventos; +} + +/** + * Genera eventos de calendario a partir de las obligaciones reales de un contribuyente. + * Usado en tenants despacho — reemplaza el catálogo estático por las obligaciones + * registradas en obligaciones_contribuyente con su estado de cumplimiento en obligacion_periodos. + */ +export async function generarEventosDesdeObligaciones( + pool: Pool, + contribuyenteId: string | null, + año: number, +): Promise { + if (!contribuyenteId) return []; + + const inhabiles = await getDiasInhabiles(año); + const obligaciones = await getObligaciones(pool, contribuyenteId); + const activas = obligaciones.filter(o => o.activa); + const eventos: EventoGenerado[] = []; + + // Get completion records for this contribuyente + const { rows: completions } = await pool.query(` + SELECT op.obligacion_id, op.periodo, op.completada + FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.contribuyente_id = $1 + `, [contribuyenteId]); + + const completionMap = new Map(); + for (const c of completions) { + completionMap.set(`${c.obligacion_id}:${c.periodo}`, c.completada); + } + + const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']; + + for (const ob of activas) { + const freq = ob.frecuencia || 'mensual'; + + // Determine which months this obligation applies to + const monthsToGenerate: number[] = []; + for (let m = 1; m <= 12; m++) { + if (freq === 'mensual') monthsToGenerate.push(m); + else if (freq === 'bimestral' && m % 2 === 1) monthsToGenerate.push(m); + else if (freq === 'trimestral' && [1, 4, 7, 10].includes(m)) monthsToGenerate.push(m); + else if (freq === 'anual' && (m === 3 || m === 4)) monthsToGenerate.push(m); + // 'eventual' and unknown: skip auto-generation + } + + for (const mes of monthsToGenerate) { + // Parse day from fechaLimite text; default to 17 + let diaBase = 17; + if (ob.fechaLimite) { + const matchDia = ob.fechaLimite.match(/d[íi]a?\s*(\d+)/i); + if (matchDia) diaBase = parseInt(matchDia[1]); + // "Último día" → last day of month + if (ob.fechaLimite.toLowerCase().includes('ltimo d')) diaBase = 0; + } + + // Deadline is usually next month for mensual/bimestral/trimestral obligations + let mesVencimiento = mes + 1; + let añoVencimiento = año; + if (mesVencimiento > 12) { mesVencimiento = 1; añoVencimiento++; } + + // For annual obligations the deadline month IS the month (marzo/abril) + if (freq === 'anual') { + mesVencimiento = mes; + añoVencimiento = año; + } + + const lastDayOfMonth = new Date(añoVencimiento, mesVencimiento, 0).getDate(); + const dia = diaBase === 0 ? lastDayOfMonth : Math.min(diaBase, lastDayOfMonth); + let fechaLimite = new Date(añoVencimiento, mesVencimiento - 1, dia); + fechaLimite = siguienteDiaHabil(fechaLimite, inhabiles); + + const periodo = `${año}-${String(mes).padStart(2, '0')}`; + const isCompleted = completionMap.get(`${ob.id}:${periodo}`) === true; + const isPastDue = !isCompleted && fechaLimite < new Date(); + + // Type encodes the status for calendar coloring + const tipoEvento = isCompleted + ? 'obligacion-completada' + : isPastDue + ? 'obligacion-atrasada' + : 'obligacion-pendiente'; + + eventos.push({ + titulo: ob.nombre, + tipo: tipoEvento, + fechaLimite: fechaLimite.toISOString().split('T')[0], + recurrencia: freq, + completado: isCompleted, + descripcion: `${ob.nombre} — ${meses[mes - 1]} ${año}`, + }); + } + } + + eventos.sort((a, b) => a.fechaLimite.localeCompare(b.fechaLimite)); + return eventos; +} diff --git a/apps/api/src/services/cartera.service.ts b/apps/api/src/services/cartera.service.ts new file mode 100644 index 0000000..6d3c299 --- /dev/null +++ b/apps/api/src/services/cartera.service.ts @@ -0,0 +1,160 @@ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; + +export interface CarteraRow { + id: string; + supervisorUserId: string | null; + auxiliarUserId: string | null; + parentId: string | null; + nombre: string; + descripcion: string | null; + createdAt: string; + entidadesCount?: number; + subcarterasCount?: number; +} + +const BASE_SELECT = ` + SELECT c.id, c.supervisor_user_id AS "supervisorUserId", + c.auxiliar_user_id AS "auxiliarUserId", c.parent_id AS "parentId", + c.nombre, c.descripcion, c.created_at AS "createdAt", + (SELECT count(*) FROM cartera_entidades ce WHERE ce.cartera_id = c.id)::int AS "entidadesCount", + (SELECT count(*) FROM carteras sc WHERE sc.parent_id = c.id)::int AS "subcarterasCount" + FROM carteras c +`; + +/** + * List top-level carteras (parent_id IS NULL). + * If supervisorUserId is provided, filter by that supervisor. + */ +export async function listCarteras(pool: Pool, supervisorUserId?: string): Promise { + const conditions = ['c.parent_id IS NULL']; + const params: unknown[] = []; + if (supervisorUserId) { + params.push(supervisorUserId); + conditions.push(`c.supervisor_user_id = $${params.length}`); + } + const { rows } = await pool.query( + `${BASE_SELECT} WHERE ${conditions.join(' AND ')} ORDER BY c.created_at DESC`, + params, + ); + return rows; +} + +/** + * List subcarteras of a parent cartera. + */ +export async function listSubcarteras(pool: Pool, parentId: string): Promise { + const { rows } = await pool.query( + `${BASE_SELECT} WHERE c.parent_id = $1 ORDER BY c.nombre`, + [parentId], + ); + return rows; +} + +export async function getCarteraById(pool: Pool, id: string): Promise { + const { rows } = await pool.query(`${BASE_SELECT} WHERE c.id = $1`, [id]); + return rows[0] ?? null; +} + +export async function createCartera(pool: Pool, data: { + supervisorUserId: string; + nombre: string; + descripcion?: string; +}): Promise { + const { rows: [row] } = await pool.query(` + INSERT INTO carteras (supervisor_user_id, nombre, descripcion) + VALUES ($1, $2, $3) RETURNING id + `, [data.supervisorUserId, data.nombre, data.descripcion ?? null]); + return (await getCarteraById(pool, row.id))!; +} + +/** + * Create a subcartera within a parent cartera, assigned to an auxiliar. + */ +export async function createSubcartera(pool: Pool, data: { + parentId: string; + auxiliarUserId: string; + nombre: string; + descripcion?: string; +}): Promise { + const { rows: [row] } = await pool.query(` + INSERT INTO carteras (parent_id, auxiliar_user_id, nombre, descripcion) + VALUES ($1, $2, $3, $4) RETURNING id + `, [data.parentId, data.auxiliarUserId, data.nombre, data.descripcion ?? null]); + return (await getCarteraById(pool, row.id))!; +} + +export async function updateCartera(pool: Pool, id: string, data: { + nombre?: string; + descripcion?: string; + supervisorUserId?: string; +}): Promise { + const existing = await getCarteraById(pool, id); + if (!existing) return null; + const sets: string[] = []; + const vals: unknown[] = []; + let idx = 1; + if (data.nombre !== undefined) { sets.push(`nombre = $${idx}`); vals.push(data.nombre); idx++; } + if (data.descripcion !== undefined) { sets.push(`descripcion = $${idx}`); vals.push(data.descripcion); idx++; } + if (data.supervisorUserId !== undefined) { sets.push(`supervisor_user_id = $${idx}`); vals.push(data.supervisorUserId); idx++; } + if (sets.length === 0) return existing; + vals.push(id); + await pool.query(`UPDATE carteras SET ${sets.join(', ')} WHERE id = $${idx}`, vals); + return (await getCarteraById(pool, id))!; +} + +export async function deleteCartera(pool: Pool, id: string): Promise { + const { rowCount } = await pool.query('DELETE FROM carteras WHERE id = $1', [id]); + return (rowCount ?? 0) > 0; +} + +// Entidades in cartera +export async function addEntidadToCartera(pool: Pool, carteraId: string, entidadId: string): Promise { + await pool.query('INSERT INTO cartera_entidades (cartera_id, entidad_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', [carteraId, entidadId]); +} + +export async function removeEntidadFromCartera(pool: Pool, carteraId: string, entidadId: string): Promise { + await pool.query('DELETE FROM cartera_entidades WHERE cartera_id = $1 AND entidad_id = $2', [carteraId, entidadId]); +} + +export async function getCarteraEntidades(pool: Pool, carteraId: string): Promise { + const { rows } = await pool.query('SELECT entidad_id AS "entidadId" FROM cartera_entidades WHERE cartera_id = $1', [carteraId]); + return rows.map(r => r.entidadId); +} + +// Auxiliares assigned to a supervisor +export async function getAuxiliaresDelSupervisor(pool: Pool, supervisorUserId: string): Promise> { + const { rows } = await pool.query( + 'SELECT auxiliar_user_id AS "auxiliarUserId" FROM auxiliar_supervisores WHERE supervisor_user_id = $1', + [supervisorUserId], + ); + return rows; +} + +// Legacy auxiliares in cartera (backward compat) +export async function addAuxiliarToCartera(pool: Pool, carteraId: string, auxiliarUserId: string): Promise { + await pool.query('INSERT INTO cartera_auxiliares (cartera_id, auxiliar_user_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', [carteraId, auxiliarUserId]); +} + +export async function removeAuxiliarFromCartera(pool: Pool, carteraId: string, auxiliarUserId: string): Promise { + await pool.query('DELETE FROM cartera_auxiliares WHERE cartera_id = $1 AND auxiliar_user_id = $2', [carteraId, auxiliarUserId]); +} + +export async function getCarteraAuxiliares(pool: Pool, carteraId: string): Promise { + const { rows } = await pool.query('SELECT auxiliar_user_id AS "auxiliarUserId" FROM cartera_auxiliares WHERE cartera_id = $1', [carteraId]); + return rows.map(r => r.auxiliarUserId); +} + +// Supervisors list (for the invite form dropdown) +export async function getSupervisores(pool: Pool, tenantId: string): Promise> { + // Query tenant_memberships joined with users for supervisor role (rolId=9) + const memberships = await prisma.tenantMembership.findMany({ + where: { tenantId, rolId: 9, active: true }, + include: { user: { select: { id: true, nombre: true, email: true } } }, + }); + return memberships.map(m => ({ + userId: m.user.id, + nombre: m.user.nombre, + email: m.user.email, + })); +} diff --git a/apps/api/src/services/cfdi.service.ts b/apps/api/src/services/cfdi.service.ts new file mode 100644 index 0000000..9d98605 --- /dev/null +++ b/apps/api/src/services/cfdi.service.ts @@ -0,0 +1,769 @@ +import type { Pool } from 'pg'; +import type { Cfdi, CfdiFilters, CfdiListResponse } from '@horux/shared'; +import { markForInvalidation } from './metricas.service.js'; +import { recomputarSaldoPendiente, uuidsAfectadosPorCfdi } from '../utils/saldo.js'; + +// Common SELECT columns mapping DB → camelCase +const CFDI_SELECT = ` + id, year, month, type, uuid, serie, folio, status, + fecha_emision as "fechaEmision", + rfc_emisor_id as "rfcEmisorId", rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor", + rfc_receptor_id as "rfcReceptorId", rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor", + subtotal, subtotal_mxn as "subtotalMxn", + descuento, descuento_mxn as "descuentoMxn", + total, total_mxn as "totalMxn", + saldo_insoluto as "saldoInsoluto", + moneda, tipo_cambio as "tipoCambio", + tipo_comprobante as "tipoComprobante", + metodo_pago as "metodoPago", forma_pago as "formaPago", + uso_cfdi as "usoCfdi", + pac, fecha_cert_sat as "fechaCertSat", + fecha_cancelacion as "fechaCancelacion", + uuid_relacionado as "uuidRelacionado", + isr_retencion as "isrRetencion", isr_retencion_mxn as "isrRetencionMxn", + iva_traslado as "ivaTraslado", iva_traslado_mxn as "ivaTrasladoMxn", + iva_retencion as "ivaRetencion", iva_retencion_mxn as "ivaRetencionMxn", + ieps_traslado as "iepsTraslado", ieps_traslado_mxn as "iepsTrasladoMxn", + ieps_retencion as "iepsRetencion", ieps_retencion_mxn as "iepsRetencionMxn", + impuestos_locales_trasladado as "impuestosLocalesTrasladado", + impuestos_locales_trasladado_mxn as "impuestosLocalesTrasladoMxn", + impuestos_locales_retenidos as "impuestosLocalesRetenidos", + impuestos_locales_retenidos_mxn as "impuestosLocalesRetenidosMxn", + monto_pago as "montoPago", monto_pago_mxn as "montoPagoMxn", + fecha_pago_p as "fechaPagoP", num_parcialidad as "numParcialidad", + isr_retencion_pago as "isrRetencionPago", isr_retencion_pago_mxn as "isrRetencionPagoMxn", + iva_traslado_pago as "ivaTrasladoPago", iva_traslado_pago_mxn as "ivaTrasladoPagoMxn", + iva_retencion_pago as "ivaRetencionPago", iva_retencion_pago_mxn as "ivaRetencionPagoMxn", + ieps_traslado_pago as "iepsTrasladoPago", ieps_traslado_pago_mxn as "iepsTrasladoPagoMxn", + ieps_retencion_pago as "iepsRetencionPago", ieps_retencion_pago_mxn as "iepsRetencionPagoMxn", + saldo_pendiente as "saldoPendiente", saldo_pendiente_mxn as "saldoPendienteMxn", + fecha_liquidacion as "fechaLiquidacion", + fecha_pago as "fechaPago", + fecha_inicial_pago as "fechaInicialPago", + fecha_final_pago as "fechaFinalPago", + num_dias_pagados as "numDiasPagados", + num_seguro_social as "numSeguroSocial", puesto, + salario_base_cot_apor as "salarioBaseCotApor", + salario_base_cot_apor_mxn as "salarioBaseCotAporMxn", + salario_diario_integrado as "salarioDiarioIntegrado", + salario_diario_integrado_mxn as "salarioDiarioIntegradoMxn", + total_percepciones as "totalPercepciones", + total_percepciones_mxn as "totalPercepcionesMxn", + total_deducciones as "totalDeducciones", + total_deducciones_mxn as "totalDeduccionesMxn", + imp_retenidos_nomina as "impRetenidosNomina", + imp_retenidos_nomina_mxn as "impRetenidosNominaMxn", + otras_deducciones_nomina as "otrasDeduccionesNomina", + otras_deducciones_nomina_mxn as "otrasDeduccionesNominaMxn", + subsidio_causado as "subsidioCausado", + subsidio_causado_mxn as "subsidioCausadoMxn", + conciliado, + regimen_fiscal_emisor as "regimenFiscalEmisor", + regimen_fiscal_receptor as "regimenFiscalReceptor", + xml_url as "xmlUrl", pdf_url as "pdfUrl", + xml_original as "xmlOriginal", + cfdi_tipo_relacion as "cfdiTipoRelacion", + cfdis_relacionados as "cfdisRelacionados", + last_sat_sync as "lastSatSync", + sat_sync_job_id as "satSyncJobId", + source, facturapi_id as "facturapiId", + creado_en as "creadoEn", actualizado_en as "actualizadoEn", + contribuyente_id AS "contribuyenteId" +`; + +export async function getCfdis(pool: Pool, filters: CfdiFilters): Promise { + const page = filters.page || 1; + const limit = filters.limit || 20; + const offset = (page - 1) * limit; + + let whereClause = 'WHERE 1=1'; + const params: any[] = []; + let paramIndex = 1; + + // El filtro "tipo" (EMITIDO / RECIBIDO) usa la posición del RFC del + // contribuyente cuando viene contribuyenteId — más confiable que la + // columna `type`, que puede quedar inconsistente cuando dos + // contribuyentes del mismo tenant se facturan entre sí. Se aplica + // abajo cuando ya conocemos el RFC vía la subquery. + if (filters.tipo && !filters.contribuyenteId) { + whereClause += ` AND type = $${paramIndex++}`; + params.push(filters.tipo); + } + + if (filters.tipoComprobante) { + whereClause += ` AND tipo_comprobante = $${paramIndex++}`; + params.push(filters.tipoComprobante); + } + + if (filters.estado) { + whereClause += ` AND status = $${paramIndex++}`; + params.push(filters.estado); + } + + if (filters.fechaInicio) { + whereClause += ` AND fecha_emision >= $${paramIndex++}::date`; + params.push(filters.fechaInicio); + } + + if (filters.fechaFin) { + whereClause += ` AND fecha_emision <= ($${paramIndex++}::date + interval '1 day')`; + params.push(filters.fechaFin); + } + + if (filters.rfc) { + whereClause += ` AND (rfc_emisor ILIKE $${paramIndex} OR rfc_receptor ILIKE $${paramIndex++})`; + params.push(`%${filters.rfc}%`); + } + + if (filters.emisor) { + whereClause += ` AND (rfc_emisor ILIKE $${paramIndex} OR nombre_emisor ILIKE $${paramIndex++})`; + params.push(`%${filters.emisor}%`); + } + + if (filters.receptor) { + whereClause += ` AND (rfc_receptor ILIKE $${paramIndex} OR nombre_receptor ILIKE $${paramIndex++})`; + params.push(`%${filters.receptor}%`); + } + + if (filters.search) { + whereClause += ` AND (uuid ILIKE $${paramIndex} OR nombre_emisor ILIKE $${paramIndex} OR nombre_receptor ILIKE $${paramIndex} OR rfc_emisor ILIKE $${paramIndex} OR rfc_receptor ILIKE $${paramIndex++})`; + params.push(`%${filters.search}%`); + } + + if (filters.contribuyenteId) { + // Lado del contribuyente: si filters.tipo viene, restringe a EMITIDO + // (rfc_emisor = X) o RECIBIDO (rfc_receptor = X). Si no, ambos lados + // (OR contribuyente_id = X para casos donde el RFC no quedó + // correctamente asignado pero el tenant lo poseía). + if (filters.tipo === 'EMITIDO') { + whereClause += ` AND rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`; + params.push(filters.contribuyenteId); + } else if (filters.tipo === 'RECIBIDO') { + whereClause += ` AND rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`; + params.push(filters.contribuyenteId); + } else { + whereClause += ` AND (contribuyente_id = $${paramIndex} OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex}) OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++}))`; + params.push(filters.contribuyenteId); + } + } + + params.push(limit, offset); + const { rows: dataWithCount } = await pool.query(` + SELECT ${CFDI_SELECT}, + COUNT(*) OVER() as total_count + FROM cfdis + ${whereClause} + ORDER BY fecha_emision DESC + LIMIT $${paramIndex++} OFFSET $${paramIndex} + `, params); + + const total = Number(dataWithCount[0]?.total_count || 0); + const data = dataWithCount.map(({ total_count, ...cfdi }: any) => cfdi) as Cfdi[]; + + return { + data, + total, + page, + limit, + totalPages: Math.ceil(total / limit), + }; +} + +/** + * Lista paginada de conceptos (cfdi_conceptos) — reusa los mismos filtros de + * `getCfdis` aplicados contra la tabla `cfdis` joined. Devuelve TODAS las + * columnas non-MXN del concepto + fecha/uuid/RFCs del CFDI padre, para + * alimentar la pestaña "Conceptos" en /cfdi y su export a Excel. + */ +export async function getConceptosList( + pool: Pool, + filters: CfdiFilters & { + uuidLike?: string; + claveProdServ?: string; + descripcionConcepto?: string; + orderBy?: 'fecha' | 'importe'; + orderDir?: 'asc' | 'desc'; + }, +): Promise<{ + data: any[]; + total: number; + page: number; + limit: number; + totalPages: number; +}> { + const page = filters.page || 1; + const limit = filters.limit || 50; + const offset = (page - 1) * limit; + + let whereClause = 'WHERE 1=1'; + const params: any[] = []; + let paramIndex = 1; + + if (filters.tipo && !filters.contribuyenteId) { + whereClause += ` AND c.type = $${paramIndex++}`; + params.push(filters.tipo); + } + if (filters.tipoComprobante) { + whereClause += ` AND c.tipo_comprobante = $${paramIndex++}`; + params.push(filters.tipoComprobante); + } + if (filters.estado) { + whereClause += ` AND c.status = $${paramIndex++}`; + params.push(filters.estado); + } + if (filters.fechaInicio) { + whereClause += ` AND c.fecha_emision >= $${paramIndex++}::date`; + params.push(filters.fechaInicio); + } + if (filters.fechaFin) { + whereClause += ` AND c.fecha_emision <= ($${paramIndex++}::date + interval '1 day')`; + params.push(filters.fechaFin); + } + if (filters.rfc) { + whereClause += ` AND (c.rfc_emisor ILIKE $${paramIndex} OR c.rfc_receptor ILIKE $${paramIndex++})`; + params.push(`%${filters.rfc}%`); + } + if (filters.emisor) { + whereClause += ` AND (c.rfc_emisor ILIKE $${paramIndex} OR c.nombre_emisor ILIKE $${paramIndex++})`; + params.push(`%${filters.emisor}%`); + } + if (filters.receptor) { + whereClause += ` AND (c.rfc_receptor ILIKE $${paramIndex} OR c.nombre_receptor ILIKE $${paramIndex++})`; + params.push(`%${filters.receptor}%`); + } + if (filters.search) { + whereClause += ` AND (c.uuid ILIKE $${paramIndex} OR c.nombre_emisor ILIKE $${paramIndex} OR c.nombre_receptor ILIKE $${paramIndex} OR c.rfc_emisor ILIKE $${paramIndex} OR c.rfc_receptor ILIKE $${paramIndex} OR cc.descripcion ILIKE $${paramIndex++})`; + params.push(`%${filters.search}%`); + } + if (filters.contribuyenteId) { + if (filters.tipo === 'EMITIDO') { + whereClause += ` AND c.rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`; + params.push(filters.contribuyenteId); + } else if (filters.tipo === 'RECIBIDO') { + whereClause += ` AND c.rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`; + params.push(filters.contribuyenteId); + } else { + whereClause += ` AND (c.contribuyente_id = $${paramIndex} OR c.rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex}) OR c.rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++}))`; + params.push(filters.contribuyenteId); + } + } + + // Filtros específicos de la tabla Conceptos (popovers en headers). + if (filters.uuidLike) { + whereClause += ` AND c.uuid ILIKE $${paramIndex++}`; + params.push(`%${filters.uuidLike}%`); + } + if (filters.claveProdServ) { + whereClause += ` AND cc.clave_prod_serv ILIKE $${paramIndex++}`; + params.push(`%${filters.claveProdServ}%`); + } + if (filters.descripcionConcepto) { + whereClause += ` AND cc.descripcion ILIKE $${paramIndex++}`; + params.push(`%${filters.descripcionConcepto}%`); + } + + // Ordenamiento configurable. Default: fecha DESC, id ASC (estable). + const orderDir = filters.orderDir === 'asc' ? 'ASC' : 'DESC'; + let orderClause = `ORDER BY c.fecha_emision ${orderDir}, cc.id ASC`; + if (filters.orderBy === 'importe') { + orderClause = `ORDER BY cc.importe ${orderDir}, cc.id ASC`; + } + + params.push(limit, offset); + // SELECT * de cfdi_conceptos para devolver todas las columnas non-MXN + // (las MXN también se traen por simplicidad — el frontend las ignora al + // exportar; el filtro "no terminen en _mxn" se aplica en el cliente). + const { rows: dataWithCount } = await pool.query(` + SELECT + c.fecha_emision AS "fechaEmision", + c.uuid AS "uuid", + c.rfc_emisor AS "rfcEmisor", + c.rfc_receptor AS "rfcReceptor", + c.nombre_emisor AS "nombreEmisor", + c.nombre_receptor AS "nombreReceptor", + c.tipo_comprobante AS "tipoComprobante", + c.status AS "status", + cc.*, + COUNT(*) OVER() AS total_count + FROM cfdi_conceptos cc + JOIN cfdis c ON c.id = cc.cfdi_id + ${whereClause} + ${orderClause} + LIMIT $${paramIndex++} OFFSET $${paramIndex} + `, params); + + const total = Number(dataWithCount[0]?.total_count || 0); + const data = dataWithCount.map(({ total_count, ...row }: any) => row); + + return { + data, + total, + page, + limit, + totalPages: Math.ceil(total / limit), + }; +} + +export async function getCfdiById(pool: Pool, id: string): Promise { + const { rows } = await pool.query(` + SELECT ${CFDI_SELECT} + FROM cfdis + WHERE id = $1 + `, [id]); + + return rows[0] || null; +} + +export async function getConceptos(pool: Pool, cfdiId: string): Promise { + const { rows } = await pool.query(` + SELECT + id, cfdi_id as "cfdiId", + clave_prod_serv as "claveProdServ", + no_identificacion as "noIdentificacion", + descripcion, cantidad, + clave_unidad as "claveUnidad", unidad, + valor_unitario as "valorUnitario", + valor_unitario_mxn as "valorUnitarioMxn", + importe, importe_mxn as "importeMxn", + descuento, descuento_mxn as "descuentoMxn", + isr_retencion as "isrRetencion", + isr_retencion_mxn as "isrRetencionMxn", + iva_traslado as "ivaTraslado", + iva_traslado_mxn as "ivaTrasladoMxn", + iva_retencion as "ivaRetencion", + iva_retencion_mxn as "ivaRetencionMxn", + ieps_traslado as "iepsTraslado", + ieps_traslado_mxn as "iepsTrasladoMxn", + ieps_retencion as "iepsRetencion", + ieps_retencion_mxn as "iepsRetencionMxn" + FROM cfdi_conceptos + WHERE cfdi_id = $1 + ORDER BY id + `, [cfdiId]); + return rows; +} + +export async function getXmlById(pool: Pool, id: string): Promise { + const { rows } = await pool.query(` + SELECT xml_original FROM cfdis WHERE id = $1 + `, [id]); + + return rows[0]?.xml_original || null; +} + +export interface CreateCfdiData { + uuid: string; + type: 'EMITIDO' | 'RECIBIDO'; + serie?: string; + folio?: string; + status?: string; + fechaEmision: string; + rfcEmisor: string; + nombreEmisor: string; + rfcReceptor: string; + nombreReceptor: string; + subtotal: number; + subtotalMxn?: number; + descuento?: number; + descuentoMxn?: number; + total: number; + totalMxn?: number; + saldoInsoluto?: string; + moneda?: string; + tipoCambio?: number; + tipoComprobante?: string; + metodoPago?: string; + formaPago?: string; + usoCfdi?: string; + pac?: string; + fechaCertSat?: string; + fechaCancelacion?: string; + uuidRelacionado?: string; + isrRetencion?: number; + isrRetencionMxn?: number; + ivaTraslado?: number; + ivaTrasladoMxn?: number; + ivaRetencion?: number; + ivaRetencionMxn?: number; + iepsTraslado?: number; + iepsTrasladoMxn?: number; + iepsRetencion?: number; + iepsRetencionMxn?: number; + impuestosLocalesTrasladado?: number; + impuestosLocalesTrasladoMxn?: number; + impuestosLocalesRetenidos?: number; + impuestosLocalesRetenidosMxn?: number; + montoPago?: number; + montoPagoMxn?: number; + fechaPagoP?: string; + numParcialidad?: string; + isrRetencionPago?: number; + isrRetencionPagoMxn?: number; + ivaTrasladoPago?: number; + ivaTrasladoPagoMxn?: number; + ivaRetencionPago?: number; + ivaRetencionPagoMxn?: number; + iepsTrasladoPago?: number; + iepsTrasladoPagoMxn?: number; + iepsRetencionPago?: number; + iepsRetencionPagoMxn?: number; + saldoPendiente?: number; + saldoPendienteMxn?: number; + fechaLiquidacion?: string; + fechaPago?: string; + fechaInicialPago?: string; + fechaFinalPago?: string; + numDiasPagados?: number; + numSeguroSocial?: string; + puesto?: string; + salarioBaseCotApor?: number; + salarioBaseCotAporMxn?: number; + salarioDiarioIntegrado?: number; + salarioDiarioIntegradoMxn?: number; + totalPercepciones?: number; + totalPercepcionesMxn?: number; + totalDeducciones?: number; + totalDeduccionesMxn?: number; + impRetenidosNomina?: number; + impRetenidosNominaMxn?: number; + otrasDeduccionesNomina?: number; + otrasDeduccionesNominaMxn?: number; + subsidioCausado?: number; + subsidioCausadoMxn?: number; + conciliado?: string; + regimenFiscalEmisor?: string; + regimenFiscalReceptor?: string; + xmlUrl?: string; + pdfUrl?: string; + xmlOriginal?: string; + cfdiTipoRelacion?: string; + cfdisRelacionados?: string; + source?: string; + contribuyenteId?: string; +} + +function computeMxn(value: number | undefined, tipoCambio: number): number { + return (value || 0) * tipoCambio; +} + +export async function createCfdi(pool: Pool, data: CreateCfdiData): Promise { + if (!data.uuid) throw new Error('UUID es requerido'); + if (!data.fechaEmision) throw new Error('Fecha de emisión es requerida'); + if (!data.rfcEmisor) throw new Error('RFC Emisor es requerido'); + if (!data.rfcReceptor) throw new Error('RFC Receptor es requerido'); + + const dateStr = typeof data.fechaEmision === 'string' && data.fechaEmision.match(/^\d{4}-\d{2}-\d{2}$/) + ? `${data.fechaEmision}T12:00:00` + : data.fechaEmision; + + const fechaEmision = new Date(dateStr); + if (isNaN(fechaEmision.getTime())) { + throw new Error(`Fecha de emisión inválida: ${data.fechaEmision}`); + } + + const year = String(fechaEmision.getFullYear()); + const month = String(fechaEmision.getMonth() + 1).padStart(2, '0'); + const tc = data.tipoCambio || 1; + + const { rows } = await pool.query(` + INSERT INTO cfdis ( + year, month, type, uuid, serie, folio, status, fecha_emision, + rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor, + subtotal, subtotal_mxn, descuento, descuento_mxn, + total, total_mxn, saldo_insoluto, moneda, tipo_cambio, + tipo_comprobante, metodo_pago, forma_pago, uso_cfdi, + pac, fecha_cert_sat, fecha_cancelacion, uuid_relacionado, + isr_retencion, isr_retencion_mxn, iva_traslado, iva_traslado_mxn, + iva_retencion, iva_retencion_mxn, ieps_traslado, ieps_traslado_mxn, + ieps_retencion, ieps_retencion_mxn, + impuestos_locales_trasladado, impuestos_locales_trasladado_mxn, + impuestos_locales_retenidos, impuestos_locales_retenidos_mxn, + monto_pago, monto_pago_mxn, fecha_pago_p, num_parcialidad, + isr_retencion_pago, isr_retencion_pago_mxn, + iva_traslado_pago, iva_traslado_pago_mxn, + iva_retencion_pago, iva_retencion_pago_mxn, + ieps_traslado_pago, ieps_traslado_pago_mxn, + ieps_retencion_pago, ieps_retencion_pago_mxn, + saldo_pendiente, saldo_pendiente_mxn, + fecha_liquidacion, fecha_pago, fecha_inicial_pago, fecha_final_pago, + num_dias_pagados, num_seguro_social, puesto, + salario_base_cot_apor, salario_base_cot_apor_mxn, + salario_diario_integrado, salario_diario_integrado_mxn, + total_percepciones, total_percepciones_mxn, + total_deducciones, total_deducciones_mxn, + imp_retenidos_nomina, imp_retenidos_nomina_mxn, + otras_deducciones_nomina, otras_deducciones_nomina_mxn, + subsidio_causado, subsidio_causado_mxn, + conciliado, + regimen_fiscal_emisor, regimen_fiscal_receptor, + xml_url, pdf_url, xml_original, + cfdi_tipo_relacion, cfdis_relacionados, + source, + contribuyente_id + ) VALUES ( + $1,$2,$3,$4,$5,$6,$7,$8,$9,$10, + $11,$12,$13,$14,$15,$16,$17,$18,$19,$20, + $21,$22,$23,$24,$25,$26,$27,$28,$29,$30, + $31,$32,$33,$34,$35,$36,$37,$38,$39,$40, + $41,$42,$43,$44,$45,$46,$47,$48,$49,$50, + $51,$52,$53,$54,$55,$56,$57,$58,$59,$60, + $61,$62,$63,$64,$65,$66,$67,$68,$69,$70, + $71,$72,$73,$74,$75,$76,$77,$78,$79,$80, + $81,$82,$83,$84,$85,$86, + $87,$88, + $89 + ) + RETURNING ${CFDI_SELECT} + `, [ + year, month, + data.type || 'ingreso', + data.uuid, + data.serie || null, + data.folio || null, + data.status || 'vigente', + fechaEmision, + data.rfcEmisor, + data.nombreEmisor || 'Sin nombre', + data.rfcReceptor, + data.nombreReceptor || 'Sin nombre', + data.subtotal || 0, + data.subtotalMxn ?? computeMxn(data.subtotal, tc), + data.descuento || 0, + data.descuentoMxn ?? computeMxn(data.descuento, tc), + data.total || 0, + data.totalMxn ?? computeMxn(data.total, tc), + data.saldoInsoluto || null, + data.moneda || 'MXN', + tc, + data.tipoComprobante || null, + data.metodoPago || null, + data.formaPago || null, + data.usoCfdi || null, + data.pac || null, + data.fechaCertSat || null, + data.fechaCancelacion || null, + data.uuidRelacionado || null, + data.isrRetencion || 0, + data.isrRetencionMxn ?? computeMxn(data.isrRetencion, tc), + data.ivaTraslado || 0, + data.ivaTrasladoMxn ?? computeMxn(data.ivaTraslado, tc), + data.ivaRetencion || 0, + data.ivaRetencionMxn ?? computeMxn(data.ivaRetencion, tc), + data.iepsTraslado || 0, + data.iepsTrasladoMxn ?? computeMxn(data.iepsTraslado, tc), + data.iepsRetencion || 0, + data.iepsRetencionMxn ?? computeMxn(data.iepsRetencion, tc), + data.impuestosLocalesTrasladado || 0, + data.impuestosLocalesTrasladoMxn ?? computeMxn(data.impuestosLocalesTrasladado, tc), + data.impuestosLocalesRetenidos || 0, + data.impuestosLocalesRetenidosMxn ?? computeMxn(data.impuestosLocalesRetenidos, tc), + data.montoPago || 0, + data.montoPagoMxn ?? computeMxn(data.montoPago, tc), + data.fechaPagoP || null, + data.numParcialidad || null, + data.isrRetencionPago || 0, + data.isrRetencionPagoMxn ?? computeMxn(data.isrRetencionPago, tc), + data.ivaTrasladoPago || 0, + data.ivaTrasladoPagoMxn ?? computeMxn(data.ivaTrasladoPago, tc), + data.ivaRetencionPago || 0, + data.ivaRetencionPagoMxn ?? computeMxn(data.ivaRetencionPago, tc), + data.iepsTrasladoPago || 0, + data.iepsTrasladoPagoMxn ?? computeMxn(data.iepsTrasladoPago, tc), + data.iepsRetencionPago || 0, + data.iepsRetencionPagoMxn ?? computeMxn(data.iepsRetencionPago, tc), + data.saldoPendiente || 0, + data.saldoPendienteMxn ?? computeMxn(data.saldoPendiente, tc), + data.fechaLiquidacion || null, + data.fechaPago || null, + data.fechaInicialPago || null, + data.fechaFinalPago || null, + data.numDiasPagados || 0, + data.numSeguroSocial || null, + data.puesto || null, + data.salarioBaseCotApor || 0, + data.salarioBaseCotAporMxn ?? computeMxn(data.salarioBaseCotApor, tc), + data.salarioDiarioIntegrado || 0, + data.salarioDiarioIntegradoMxn ?? computeMxn(data.salarioDiarioIntegrado, tc), + data.totalPercepciones || 0, + data.totalPercepcionesMxn ?? computeMxn(data.totalPercepciones, tc), + data.totalDeducciones || 0, + data.totalDeduccionesMxn ?? computeMxn(data.totalDeducciones, tc), + data.impRetenidosNomina || 0, + data.impRetenidosNominaMxn ?? computeMxn(data.impRetenidosNomina, tc), + data.otrasDeduccionesNomina || 0, + data.otrasDeduccionesNominaMxn ?? computeMxn(data.otrasDeduccionesNomina, tc), + data.subsidioCausado || 0, + data.subsidioCausadoMxn ?? computeMxn(data.subsidioCausado, tc), + data.conciliado || null, + data.regimenFiscalEmisor || null, + data.regimenFiscalReceptor || null, + data.xmlUrl || null, + data.pdfUrl || null, + data.xmlOriginal || null, + data.cfdiTipoRelacion || null, + data.cfdisRelacionados || null, + data.source || 'manual', + data.contribuyenteId ?? null, + ]); + + // Retroactive invalidation hook: mark cached metrics stale for prior-year CFDIs + try { + const cfdiDate = new Date(data.fechaEmision || new Date()); + const cfdiYear = cfdiDate.getFullYear(); + const currentYear = new Date().getFullYear(); + if (cfdiYear < currentYear && data.contribuyenteId) { + await markForInvalidation(pool, data.contribuyenteId, cfdiYear, cfdiDate.getMonth() + 1, 'CFDI_INSERT'); + } + } catch (err) { + console.error('[Metricas] Invalidation hook failed (non-blocking):', err); + } + + // Recompute saldo_pendiente_mxn de los CFDIs afectados por este insert. + // Un I PPD recalcula su propio saldo (considera anticipo si es I/07); un + // P o E no-07 recalcula los I PPD que referencia. + try { + const afectados = uuidsAfectadosPorCfdi({ + uuid: data.uuid!, + tipoComprobante: data.tipoComprobante ?? null, + metodoPago: data.metodoPago ?? null, + cfdiTipoRelacion: data.cfdiTipoRelacion ?? null, + uuidRelacionado: data.uuidRelacionado ?? null, + cfdisRelacionados: data.cfdisRelacionados ?? null, + }); + if (afectados.length > 0) { + await recomputarSaldoPendiente(pool, afectados); + } + } catch (err) { + console.error('[Saldo] Recompute hook failed (non-blocking):', err); + } + + return rows[0]; +} + +export interface BatchInsertResult { + inserted: number; + duplicates: number; + errors: number; + errorMessages: string[]; +} + +export async function createManyCfdis(pool: Pool, cfdis: CreateCfdiData[]): Promise { + const result = await createManyCfdisBatch(pool, cfdis); + return result.inserted; +} + +export async function createManyCfdisBatch(pool: Pool, cfdis: CreateCfdiData[]): Promise { + const result: BatchInsertResult = { + inserted: 0, + duplicates: 0, + errors: 0, + errorMessages: [] + }; + + if (cfdis.length === 0) return result; + + for (const cfdi of cfdis) { + try { + await createCfdi(pool, cfdi); + result.inserted++; + } catch (error: any) { + const errorMsg = error.message || 'Error desconocido'; + if (errorMsg.includes('duplicate') || errorMsg.includes('unique')) { + result.duplicates++; + } else { + result.errors++; + if (result.errorMessages.length < 10) { + result.errorMessages.push(`${cfdi.uuid?.substring(0, 8) || 'N/A'}: ${errorMsg}`); + } + } + } + } + + return result; +} + +export async function deleteCfdi(pool: Pool, id: string): Promise { + // Fetch before deleting so we can fire the invalidation hook + const { rows: pre } = await pool.query( + `SELECT fecha_emision, contribuyente_id FROM cfdis WHERE id = $1`, + [id] + ); + await pool.query(`DELETE FROM cfdis WHERE id = $1`, [id]); + + // Retroactive invalidation hook: mark cached metrics stale for prior-year CFDIs + try { + if (pre[0]) { + const cfdiDate = new Date(pre[0].fecha_emision || new Date()); + const cfdiYear = cfdiDate.getFullYear(); + const currentYear = new Date().getFullYear(); + if (cfdiYear < currentYear && pre[0].contribuyente_id) { + await markForInvalidation(pool, pre[0].contribuyente_id, cfdiYear, cfdiDate.getMonth() + 1, 'CFDI_INSERT'); + } + } + } catch (err) { + console.error('[Metricas] Invalidation hook failed (non-blocking):', err); + } +} + +export async function getEmisores(pool: Pool, search: string, limit: number = 10, contribuyenteId?: string): Promise<{ rfc: string; nombre: string }[]> { + let whereClause = 'WHERE rfc_emisor ILIKE $1 OR nombre_emisor ILIKE $1'; + const params: any[] = [`%${search}%`, limit]; + if (contribuyenteId) { + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + whereClause += ` AND (contribuyente_id = '${safeId}' OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}') OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}'))`; + } + const { rows } = await pool.query(` + SELECT DISTINCT rfc_emisor as rfc, nombre_emisor as nombre + FROM cfdis + ${whereClause} + ORDER BY nombre_emisor + LIMIT $2 + `, params); + return rows; +} + +export async function getReceptores(pool: Pool, search: string, limit: number = 10, contribuyenteId?: string): Promise<{ rfc: string; nombre: string }[]> { + let whereClause = 'WHERE rfc_receptor ILIKE $1 OR nombre_receptor ILIKE $1'; + const params: any[] = [`%${search}%`, limit]; + if (contribuyenteId) { + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + whereClause += ` AND (contribuyente_id = '${safeId}' OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}') OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}'))`; + } + const { rows } = await pool.query(` + SELECT DISTINCT rfc_receptor as rfc, nombre_receptor as nombre + FROM cfdis + ${whereClause} + ORDER BY nombre_receptor + LIMIT $2 + `, params); + return rows; +} + +export async function getResumenCfdis(pool: Pool, año: number, mes: number, contribuyenteId?: string) { + let whereClause = `WHERE status NOT IN ('Cancelado', '0') AND year = $1 AND month = $2`; + if (contribuyenteId) { + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + whereClause += ` AND (contribuyente_id = '${safeId}' OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}') OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}'))`; + } + const { rows } = await pool.query(` + SELECT + COALESCE(SUM(CASE WHEN type = 'EMITIDO' AND tipo_comprobante = 'I' THEN total_mxn ELSE 0 END), 0) as total_ingresos, + COALESCE(SUM(CASE WHEN type = 'RECIBIDO' AND tipo_comprobante = 'I' THEN total_mxn ELSE 0 END), 0) as total_egresos, + COUNT(CASE WHEN type = 'EMITIDO' AND tipo_comprobante = 'I' THEN 1 END) as count_ingresos, + COUNT(CASE WHEN type = 'RECIBIDO' AND tipo_comprobante = 'I' THEN 1 END) as count_egresos, + COALESCE(SUM(CASE WHEN type = 'EMITIDO' THEN iva_traslado_mxn ELSE 0 END), 0) as iva_trasladado, + COALESCE(SUM(CASE WHEN type = 'RECIBIDO' THEN iva_traslado_mxn ELSE 0 END), 0) as iva_acreditable + FROM cfdis + ${whereClause} + `, [String(año), String(mes).padStart(2, '0')]); + + const r = rows[0]; + return { + totalIngresos: Number(r?.total_ingresos || 0), + totalEgresos: Number(r?.total_egresos || 0), + countIngresos: Number(r?.count_ingresos || 0), + countEgresos: Number(r?.count_egresos || 0), + ivaTrasladado: Number(r?.iva_trasladado || 0), + ivaAcreditable: Number(r?.iva_acreditable || 0), + }; +} diff --git a/apps/api/src/services/conciliacion.service.ts b/apps/api/src/services/conciliacion.service.ts new file mode 100644 index 0000000..78360c3 --- /dev/null +++ b/apps/api/src/services/conciliacion.service.ts @@ -0,0 +1,257 @@ +import type { Pool } from 'pg'; + +const VIGENTE = `status NOT IN ('Cancelado', '0')`; + +export interface ConciliacionCfdi { + id: number; + uuid: string; + type: string; + fechaEmision: string; + rfcEmisor: string; + nombreEmisor: string; + rfcReceptor: string; + nombreReceptor: string; + total: number; + totalMxn: number; + metodoPago: string | null; + conciliado: string | null; + idConciliacion: number | null; + conciliacion: { + id: number; + fechaDePago: string; + banco: string; + terminacionCuenta: string; + } | null; +} + +export async function getCfdisConConciliacion( + pool: Pool, + filters: { + tipo: string; + fechaInicio?: string; + fechaFin?: string; + regimen?: string; + estado?: string; + contribuyenteId?: string; + } +): Promise { + const params: any[] = []; + let idx = 1; + + let where = `WHERE c.type = $${idx++} AND c.${VIGENTE}`; + params.push(filters.tipo); + + // Excluir PPD en recibidos + if (filters.tipo === 'RECIBIDO') { + where += ` AND (c.metodo_pago IS NULL OR c.metodo_pago != 'PPD')`; + } + + // Excluir PPD en emitidos para todos los regimenes excepto 605 y 616 + if (filters.tipo === 'EMITIDO') { + where += ` AND NOT (c.metodo_pago = 'PPD' AND (c.regimen_fiscal_emisor IS NULL OR c.regimen_fiscal_emisor NOT IN ('605','616')))`; + } + + if (filters.fechaInicio) { + where += ` AND c.fecha_emision >= $${idx++}::date`; + params.push(filters.fechaInicio); + } + if (filters.fechaFin) { + where += ` AND c.fecha_emision <= ($${idx++}::date + interval '1 day')`; + params.push(filters.fechaFin); + } + if (filters.regimen) { + const regimenCol = filters.tipo === 'EMITIDO' ? 'regimen_fiscal_emisor' : 'regimen_fiscal_receptor'; + where += ` AND c.${regimenCol} = $${idx++}`; + params.push(filters.regimen); + } + if (filters.estado === 'conciliado') { + where += ` AND c.conciliado = 'true'`; + } else if (filters.estado === 'pendiente') { + where += ` AND (c.conciliado IS NULL OR c.conciliado != 'true')`; + } + + if (filters.contribuyenteId) { + const safeId = filters.contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + where += ` AND c.contribuyente_id = '${safeId}'`; + } + + const { rows } = await pool.query(` + SELECT + c.id, c.uuid, c.type, + c.fecha_emision as "fechaEmision", + c.rfc_emisor as "rfcEmisor", c.nombre_emisor as "nombreEmisor", + c.rfc_receptor as "rfcReceptor", c.nombre_receptor as "nombreReceptor", + c.total, c.total_mxn as "totalMxn", + c.tipo_comprobante as "tipoComprobante", + c.monto_pago_mxn as "montoPagoMxn", + c.metodo_pago as "metodoPago", + c.conciliado, + c.id_conciliacion as "idConciliacion", + con.id as "conId", + con.fecha_de_pago as "conFechaDePago", + b.banco as "conBanco", + b.terminacion_cuenta as "conTerminacionCuenta" + FROM cfdis c + LEFT JOIN conciliaciones con ON con.id_cfdi = c.id + LEFT JOIN bancos b ON b.id = con.id_banco + ${where} + ORDER BY c.fecha_emision DESC + `, params); + + return rows.map((r: any) => ({ + id: r.id, + uuid: r.uuid, + type: r.type, + fechaEmision: r.fechaEmision, + rfcEmisor: r.rfcEmisor, + nombreEmisor: r.nombreEmisor, + rfcReceptor: r.rfcReceptor, + nombreReceptor: r.nombreReceptor, + total: Number(r.total), + totalMxn: Number(r.totalMxn), + tipoComprobante: r.tipoComprobante, + montoPagoMxn: Number(r.montoPagoMxn || 0), + // P usa monto_pago_mxn, PPD conciliada no suma (evitar duplicar con su P), resto usa total_mxn + montoMxn: r.tipoComprobante === 'P' + ? Number(r.montoPagoMxn || 0) + : (r.metodoPago === 'PPD' && r.conciliado === 'true') ? 0 : Number(r.totalMxn || 0), + metodoPago: r.metodoPago, + conciliado: r.conciliado, + idConciliacion: r.idConciliacion, + conciliacion: r.conId ? { + id: r.conId, + fechaDePago: r.conFechaDePago, + banco: r.conBanco, + terminacionCuenta: r.conTerminacionCuenta, + } : null, + })); +} + +export async function conciliar( + pool: Pool, + data: { cfdiIds: number[]; fechaDePago: string; idBanco: number }, + tenantCreatedYear: number, +): Promise { + const fechaPago = new Date(data.fechaDePago + 'T12:00:00'); + const anio = String(fechaPago.getFullYear()); + const mes = String(fechaPago.getMonth() + 1).padStart(2, '0'); + + if (fechaPago.getFullYear() < tenantCreatedYear) { + throw new Error(`Solo se puede conciliar del año ${tenantCreatedYear} en adelante`); + } + + const { rows: bancoRows } = await pool.query(`SELECT id FROM bancos WHERE id = $1`, [data.idBanco]); + if (bancoRows.length === 0) throw new Error('Banco no encontrado'); + + const { rows: cfdis } = await pool.query(` + SELECT id, conciliado FROM cfdis + WHERE id = ANY($1) AND ${VIGENTE} + `, [data.cfdiIds]); + + if (cfdis.length !== data.cfdiIds.length) { + throw new Error('Algunos CFDIs no existen o estan cancelados'); + } + + const yaConc = cfdis.filter((c: any) => c.conciliado === 'true'); + if (yaConc.length > 0) { + throw new Error(`${yaConc.length} CFDIs ya estan conciliados`); + } + + let count = 0; + for (const cfdiId of data.cfdiIds) { + const { rows: inserted } = await pool.query(` + INSERT INTO conciliaciones (anio, mes, id_cfdi, fecha_de_pago, id_banco) + VALUES ($1, $2, $3, $4, $5) + RETURNING id + `, [anio, mes, cfdiId, data.fechaDePago, data.idBanco]); + + await pool.query(` + UPDATE cfdis SET conciliado = 'true', id_conciliacion = $1 WHERE id = $2 + `, [inserted[0].id, cfdiId]); + + count++; + + // Auto-conciliar PPD si esta factura tipo P lleva saldo pendiente a 0 + await autoConciliarPpd(pool, cfdiId, anio, mes, data.fechaDePago, data.idBanco); + } + + return count; +} + +/** + * Cuando se concilia una factura tipo P con saldo_pendiente = 0, + * auto-concilia la factura PPD original relacionada con los mismos datos. + */ +async function autoConciliarPpd( + pool: Pool, + cfdiId: number, + anio: string, + mes: string, + fechaDePago: string, + idBanco: number, +): Promise { + // Verificar si es tipo P con saldo pendiente 0 + const { rows: pRows } = await pool.query(` + SELECT tipo_comprobante, uuid_relacionado, saldo_pendiente + FROM cfdis WHERE id = $1 + `, [cfdiId]); + + const pCfdi = pRows[0]; + if (!pCfdi || pCfdi.tipo_comprobante !== 'P') return; + if (!pCfdi.uuid_relacionado) return; + + const saldoPendiente = Number(pCfdi.saldo_pendiente || 0); + if (saldoPendiente !== 0) return; + + // Buscar la factura PPD original por UUID + const { rows: ppdRows } = await pool.query(` + SELECT id, conciliado FROM cfdis + WHERE uuid = $1 AND metodo_pago = 'PPD' AND ${VIGENTE} + `, [pCfdi.uuid_relacionado]); + + if (ppdRows.length === 0) return; + const ppd = ppdRows[0]; + if (ppd.conciliado === 'true') return; // ya conciliada + + // Auto-conciliar la PPD con los mismos datos + const { rows: inserted } = await pool.query(` + INSERT INTO conciliaciones (anio, mes, id_cfdi, fecha_de_pago, id_banco) + VALUES ($1, $2, $3, $4, $5) + RETURNING id + `, [anio, mes, ppd.id, fechaDePago, idBanco]); + + await pool.query(` + UPDATE cfdis SET conciliado = 'true', id_conciliacion = $1 WHERE id = $2 + `, [inserted[0].id, ppd.id]); +} + +export async function desconciliar(pool: Pool, conciliacionId: number): Promise { + // Buscar la conciliacion y el CFDI asociado + const { rows } = await pool.query(` + SELECT con.id_cfdi, c.tipo_comprobante, c.uuid_relacionado + FROM conciliaciones con + JOIN cfdis c ON c.id = con.id_cfdi + WHERE con.id = $1 + `, [conciliacionId]); + if (rows.length === 0) throw new Error('Conciliacion no encontrada'); + + const cfdi = rows[0]; + + // Desconciliar el CFDI principal + await pool.query(`UPDATE cfdis SET conciliado = NULL, id_conciliacion = NULL WHERE id_conciliacion = $1`, [conciliacionId]); + await pool.query(`DELETE FROM conciliaciones WHERE id = $1`, [conciliacionId]); + + // Si es tipo P, también desconciliar la PPD auto-conciliada + if (cfdi.tipo_comprobante === 'P' && cfdi.uuid_relacionado) { + const { rows: ppdRows } = await pool.query(` + SELECT c.id_conciliacion FROM cfdis c + WHERE c.uuid = $1 AND c.conciliado = 'true' AND c.metodo_pago = 'PPD' + `, [cfdi.uuid_relacionado]); + + if (ppdRows.length > 0 && ppdRows[0].id_conciliacion) { + const ppdConcId = ppdRows[0].id_conciliacion; + await pool.query(`UPDATE cfdis SET conciliado = NULL, id_conciliacion = NULL WHERE id_conciliacion = $1`, [ppdConcId]); + await pool.query(`DELETE FROM conciliaciones WHERE id = $1`, [ppdConcId]); + } + } +} diff --git a/apps/api/src/services/connector.service.ts b/apps/api/src/services/connector.service.ts new file mode 100644 index 0000000..d4db807 --- /dev/null +++ b/apps/api/src/services/connector.service.ts @@ -0,0 +1,156 @@ +import { prisma } from '../config/database.js'; +import { encryptAesGcm, decryptAesGcm, deriveAesKey } from '@horux/core'; +import { env } from '../config/env.js'; +import { randomBytes } from 'crypto'; + +function getEncryptionKey(): Buffer { + const secret = env.CONNECTOR_ENCRYPTION_KEY || env.FIEL_ENCRYPTION_KEY; + return deriveAesKey(secret); +} + +export async function provisionConnector(tenantId: string): Promise<{ + tunnelHostname: string; + horuxToken: string; + dockerRunCommand: string; +}> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { nombre: true, rfc: true, dbMode: true }, + }); + if (!tenant) throw new Error('Tenant no encontrado'); + + const slug = tenant.rfc.toLowerCase().replace(/[^a-z0-9]/g, ''); + const tunnelDomain = env.CLOUDFLARE_TUNNEL_DOMAIN || 'tunnel.horux.mx'; + const hostname = `${slug}.${tunnelDomain}`; + + // Generate a secure token for the connector + const horuxToken = randomBytes(32).toString('hex'); + + // Encrypt the token for storage — format: iv(16) + ciphertext + tag(16), base64 + const key = getEncryptionKey(); + const { encrypted, iv, tag } = encryptAesGcm(Buffer.from(horuxToken, 'utf-8'), key); + const tokenEncoded = Buffer.concat([iv, encrypted, tag]).toString('base64'); + + // TODO: Call Cloudflare API to create tunnel when CLOUDFLARE_API_TOKEN is configured + // For now, store the config and let the user manually configure cloudflared + if (env.CLOUDFLARE_API_TOKEN) { + console.log(`[Connector] Would create Cloudflare tunnel for ${hostname} — API integration pending`); + } + + await prisma.tenant.update({ + where: { id: tenantId }, + data: { + dbMode: 'BYO', + connectorTokenEnc: tokenEncoded, + connectorTunnelHostname: hostname, + }, + }); + + const dockerRunCommand = [ + 'docker run -d --name horux-connector', + ` -e HORUX_TOKEN="${horuxToken}"`, + ` -e HORUX_API_URL="${env.CORS_ORIGIN || 'https://horuxfin.com'}"`, + ' -e POSTGRES_HOST="localhost"', + ' -e POSTGRES_PORT="5432"', + ' horux/connector:latest', + ].join(' \\\n'); + + return { tunnelHostname: hostname, horuxToken, dockerRunCommand }; +} + +export async function recordHeartbeat(tenantId: string, data: { + version: string; + uptimeSeconds: number; + postgresPingMs: number; + pgVersion?: string; + lastMigration?: string; + status?: string; + errorMsg?: string; +}): Promise { + await Promise.all([ + prisma.connectorHeartbeat.create({ + data: { + tenantId, + latencyMs: data.postgresPingMs, + version: data.version, + pgVersion: data.pgVersion, + status: data.status || 'OK', + errorMsg: data.errorMsg, + }, + }), + prisma.tenant.update({ + where: { id: tenantId }, + data: { + connectorLastSeen: new Date(), + connectorVersion: data.version, + }, + }), + ]); +} + +export async function verifyConnectorToken(token: string): Promise { + // Find tenant by trying to decrypt stored tokens. + // This is O(N) — for production, use a hashed token lookup table. + const tenants = await prisma.tenant.findMany({ + where: { dbMode: 'BYO', connectorTokenEnc: { not: null } }, + select: { id: true, connectorTokenEnc: true }, + }); + + const key = getEncryptionKey(); + // Stored format: iv(16 bytes) + ciphertext + tag(16 bytes), base64-encoded + const IV_LENGTH = 16; + const TAG_LENGTH = 16; + + for (const t of tenants) { + if (!t.connectorTokenEnc) continue; + try { + const blob = Buffer.from(t.connectorTokenEnc, 'base64'); + const iv = blob.subarray(0, IV_LENGTH); + const tag = blob.subarray(blob.length - TAG_LENGTH); + const ciphertext = blob.subarray(IV_LENGTH, blob.length - TAG_LENGTH); + const decrypted = decryptAesGcm(ciphertext, iv, tag, key); + if (decrypted.toString('utf-8') === token) { + return t.id; + } + } catch { + continue; + } + } + + return null; +} + +export async function getConnectorStatus(tenantId: string): Promise<{ + configured: boolean; + tunnelHostname?: string; + lastSeen?: string; + version?: string; + status: 'connected' | 'degraded' | 'disconnected' | 'not_configured'; +}> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { dbMode: true, connectorTunnelHostname: true, connectorLastSeen: true, connectorVersion: true }, + }); + + if (!tenant || tenant.dbMode !== 'BYO' || !tenant.connectorTunnelHostname) { + return { configured: false, status: 'not_configured' }; + } + + const lastSeen = tenant.connectorLastSeen; + const now = new Date(); + let status: 'connected' | 'degraded' | 'disconnected' = 'disconnected'; + + if (lastSeen) { + const diffMs = now.getTime() - lastSeen.getTime(); + if (diffMs < 60_000) status = 'connected'; + else if (diffMs < 300_000) status = 'degraded'; + } + + return { + configured: true, + tunnelHostname: tenant.connectorTunnelHostname ?? undefined, + lastSeen: lastSeen?.toISOString(), + version: tenant.connectorVersion ?? undefined, + status, + }; +} diff --git a/apps/api/src/services/constancia.service.ts b/apps/api/src/services/constancia.service.ts new file mode 100644 index 0000000..d49fbed --- /dev/null +++ b/apps/api/src/services/constancia.service.ts @@ -0,0 +1,402 @@ +import { chromium } from 'playwright'; +import { writeFileSync, unlinkSync, mkdirSync, rmdirSync } from 'fs'; +import { join } from 'path'; +import { tmpdir } from 'os'; +import { randomUUID } from 'crypto'; +import type { Pool } from 'pg'; +import { prisma, tenantDb } from '../config/database.js'; +import { getDecryptedFiel } from './fiel.service.js'; +import { getDecryptedFielContribuyente } from './contribuyente-fiel.service.js'; +import { loginSatCsf } from './sat/sat-csf-login.js'; +import { extractCsfPdf } from './sat/sat-csf-scraper.js'; +import { parseCsfPdf, type ConstanciaSituacionFiscal, type Domicilio, type RegimenCsf } from './sat/sat-csf-parser.js'; + +const PROCESS_TIMEOUT = 180_000; + +export interface ConstanciaRow { + id: number; + rfc: string; + idCif: string | null; + razonSocial: string | null; + estatusPadron: string | null; + fechaEmision: string | null; + datos: ConstanciaSituacionFiscal; + fechaConsulta: string; + createdAt: string; +} + +function rowToConstancia(r: any): ConstanciaRow { + return { + id: r.id, + rfc: r.rfc, + idCif: r.id_cif, + razonSocial: r.razon_social, + estatusPadron: r.estatus_padron, + fechaEmision: r.fecha_emision, + datos: r.datos, + fechaConsulta: r.fecha_consulta.toISOString(), + createdAt: r.created_at.toISOString(), + }; +} + +/** + * Descarga la CSF del portal SAT, la parsea, guarda en BD del tenant, y + * sincroniza automáticamente domicilio + regímenes activos con lo que reporta + * el SAT. El auto-fill NO es destructivo para datos custom del usuario: + * solo sobreescribe campos si la CSF tiene un valor no-vacío. + */ +export async function consultarConstancia(tenantId: string): Promise { + const fiel = await getDecryptedFiel(tenantId); + if (!fiel) throw new Error('No hay FIEL configurada o está vencida'); + + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { databaseName: true }, + }); + if (!tenant) throw new Error('Tenant no encontrado'); + + const tempId = randomUUID(); + const tempDir = join(tmpdir(), `horux-csf-${tempId}`); + mkdirSync(tempDir, { recursive: true, mode: 0o700 }); + const cerPath = join(tempDir, 'cert.cer'); + const keyPath = join(tempDir, 'key.key'); + + try { + writeFileSync(cerPath, Buffer.from(fiel.cerContent, 'binary'), { mode: 0o600 }); + writeFileSync(keyPath, Buffer.from(fiel.keyContent, 'binary'), { mode: 0o600 }); + + // Headless por default. El fix de dispatchEvent en sat-csf-login cubre el + // caso donde el click sintético no dispara el handler del SAT. Si algún + // ambiente necesita ver el browser (debug), setear SAT_HEADLESS=false. + const headless = process.env.SAT_HEADLESS !== 'false'; + const browser = await chromium.launch({ headless }); + try { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout: proceso de CSF excedió 3 minutos')), PROCESS_TIMEOUT), + ); + + const resultPromise = (async () => { + const session = await loginSatCsf(browser, cerPath, keyPath, fiel.password); + const pdfBuffer = await extractCsfPdf(session); + const csf = await parseCsfPdf(pdfBuffer); + + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + const { rows } = await pool.query( + `INSERT INTO constancias_situacion_fiscal + (rfc, id_cif, razon_social, estatus_padron, fecha_emision, datos, pdf) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, rfc, id_cif, razon_social, estatus_padron, fecha_emision, + datos, fecha_consulta, created_at`, + [ + csf.rfc, + csf.idCIF, + csf.razonSocial ?? [csf.nombre, csf.primerApellido, csf.segundoApellido].filter(Boolean).join(' ') ?? null, + csf.estatusPadron, + csf.lugarFechaEmision, + JSON.stringify(csf), + pdfBuffer, + ], + ); + + // Auto-fill domicilio del tenant + regímenes activos desde el CSF. + // Se hace después del INSERT para que si algo falla en la sincronización + // la CSF ya quedó guardada y el usuario puede verla. + await sincronizarDatosFiscales(tenantId, csf).catch(err => { + console.error(`[CSF] Error sincronizando datos fiscales para tenant ${tenantId}:`, err); + }); + + return rowToConstancia(rows[0]); + })(); + + return await Promise.race([resultPromise, timeoutPromise]); + } finally { + await browser.close(); + } + } finally { + try { unlinkSync(cerPath); } catch { /* ok */ } + try { unlinkSync(keyPath); } catch { /* ok */ } + try { rmdirSync(tempDir); } catch { /* ok */ } + } +} + +/** + * Convierte el domicilio del CSF a los campos de `tenants` (calle compuesta + * por tipoVialidad + nombreVialidad). Solo actualiza campos cuando el CSF + * trae un valor — nunca pisa con null. + */ +function domicilioToTenantFields(d: Domicilio): Record { + const calleComponents = [d.tipoVialidad, d.nombreVialidad].filter(Boolean); + const calle = calleComponents.length > 0 ? calleComponents.join(' ') : undefined; + return { + codigoPostal: d.codigoPostal, + calle, + numExterior: d.numeroExterior, + numInterior: d.numeroInterior && d.numeroInterior.toUpperCase() !== 'SIN NUMERO' ? d.numeroInterior : undefined, + colonia: d.colonia, + ciudad: d.localidad, + municipio: d.municipio, + estado: d.entidadFederativa, + }; +} + +/** + * Matchea el nombre del régimen como aparece en la CSF contra el catálogo + * `regimenes` (clave SAT + descripción). La CSF prefija "Régimen " o + * "Régimen de " a veces, y el catálogo no — normalizamos ambos para matchear. + */ +function normalizeRegimenName(s: string): string { + return s + .normalize('NFD').replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/^r[eé]gimen\s+(?:de\s+(?:las?|los)?\s*)?/i, '') + .replace(/\s+/g, ' ') + .trim(); +} + +async function matchRegimenesToCatalogo(regimenesCsf: RegimenCsf[]): Promise { + const activos = regimenesCsf.filter(r => !r.fechaFin); + if (activos.length === 0) return []; + + const catalogo = await prisma.regimen.findMany({ where: { activo: true } }); + const ids: number[] = []; + + for (const rc of activos) { + const nNormalizado = normalizeRegimenName(rc.nombre); + const match = catalogo.find(c => { + const catNorm = normalizeRegimenName(c.descripcion); + return catNorm === nNormalizado || catNorm.includes(nNormalizado) || nNormalizado.includes(catNorm); + }); + if (match) ids.push(match.id); + } + return [...new Set(ids)]; +} + +/** + * Aplica el domicilio + regímenes activos de la CSF al tenant. Idempotente: + * se puede llamar N veces, el resultado final refleja el último CSF. + */ +export async function sincronizarDatosFiscales( + tenantId: string, + csf: ConstanciaSituacionFiscal, +): Promise<{ domicilioActualizado: boolean; regimenesSincronizados: number }> { + // 1. Domicilio + const fields = domicilioToTenantFields(csf.domicilio); + const updates: Record = {}; + for (const [k, v] of Object.entries(fields)) { + if (v && v.trim().length > 0) updates[k] = v.trim(); + } + + if (Object.keys(updates).length > 0) { + await prisma.tenant.update({ where: { id: tenantId }, data: updates }); + } + + // 2. Regímenes activos — sobreescribe la lista completa con lo que diga la CSF + const regimenIds = await matchRegimenesToCatalogo(csf.regimenes); + if (regimenIds.length > 0) { + await prisma.$transaction([ + prisma.tenantRegimenActivo.deleteMany({ where: { tenantId } }), + prisma.tenantRegimenActivo.createMany({ data: regimenIds.map(regimenId => ({ tenantId, regimenId })) }), + ]); + } + + return { + domicilioActualizado: Object.keys(updates).length > 0, + regimenesSincronizados: regimenIds.length, + }; +} + +export async function listConstancias(pool: Pool, limit = 12, rfc?: string): Promise { + const params: unknown[] = [limit]; + let rfcFilter = ''; + if (rfc) { + rfcFilter = 'WHERE rfc = $2'; + params.push(rfc); + } + const { rows } = await pool.query( + `SELECT id, rfc, id_cif, razon_social, estatus_padron, fecha_emision, + datos, fecha_consulta, created_at + FROM constancias_situacion_fiscal + ${rfcFilter} + ORDER BY fecha_consulta DESC + LIMIT $1`, + params, + ); + return rows.map(rowToConstancia); +} + +export async function getConstanciaPdf(pool: Pool, id: number): Promise { + const { rows } = await pool.query(`SELECT pdf FROM constancias_situacion_fiscal WHERE id = $1`, [id]); + return rows.length > 0 ? rows[0].pdf : null; +} + +/** + * Retención 5 años (CFF Art. 30). Se ejecuta en cron diario. + */ +export async function purgeConstanciasAntiguas(pool: Pool): Promise<{ deleted: number }> { + const { rowCount } = await pool.query( + `DELETE FROM constancias_situacion_fiscal WHERE created_at < NOW() - INTERVAL '5 years'`, + ); + return { deleted: rowCount ?? 0 }; +} + +/** + * Descarga la CSF para un contribuyente específico (modo despacho). + * Usa la FIEL almacenada en la BD del tenant en lugar de la BD central. + */ +export async function consultarConstanciaContribuyente( + pool: Pool, + contribuyenteId: string, +): Promise { + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + const fiel = await getDecryptedFielContribuyente(pool, safeId); + if (!fiel) throw new Error('No hay FIEL configurada para este contribuyente o está vencida'); + + const tempId = randomUUID(); + const tempDir = join(tmpdir(), `horux-csf-${tempId}`); + mkdirSync(tempDir, { recursive: true, mode: 0o700 }); + const cerPath = join(tempDir, 'cert.cer'); + const keyPath = join(tempDir, 'key.key'); + + try { + writeFileSync(cerPath, Buffer.from(fiel.cerContent, 'binary'), { mode: 0o600 }); + writeFileSync(keyPath, Buffer.from(fiel.keyContent, 'binary'), { mode: 0o600 }); + + const headless = process.env.SAT_HEADLESS !== 'false'; + const browser = await chromium.launch({ headless }); + try { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout: proceso de CSF excedió 3 minutos')), PROCESS_TIMEOUT), + ); + + const resultPromise = (async () => { + const session = await loginSatCsf(browser, cerPath, keyPath, fiel.password); + const pdfBuffer = await extractCsfPdf(session); + const csf = await parseCsfPdf(pdfBuffer); + + const { rows } = await pool.query( + `INSERT INTO constancias_situacion_fiscal + (rfc, id_cif, razon_social, estatus_padron, fecha_emision, datos, pdf) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, rfc, id_cif, razon_social, estatus_padron, fecha_emision, + datos, fecha_consulta, created_at`, + [ + csf.rfc, + csf.idCIF, + csf.razonSocial ?? [csf.nombre, csf.primerApellido, csf.segundoApellido].filter(Boolean).join(' ') ?? null, + csf.estatusPadron, + csf.lugarFechaEmision, + JSON.stringify(csf), + pdfBuffer, + ], + ); + + // Sync datos fiscales to contribuyente table + try { + const rawDom = csf.domicilio || {}; + + // The PDF parser sometimes captures label prefixes inside values + // when the PDF has a two-column layout. Clean them out. + function cleanDomField(val: string | undefined): string { + if (!val) return ''; + // Remove embedded label prefixes like "Nombre de la Colonia: " + return val + .replace(/^.*(?:Nombre de la Colonia|Nombre del Municipio|Nombre de la Localidad|Nombre de la Entidad|Número Exterior|Número Interior|Tipo de Vialidad|Entre Calle|Y Calle|Código Postal)\s*:\s*/i, '') + .trim(); + } + + // Extract embedded values from fields that swallowed the next column + function extractEmbedded(val: string | undefined, labelPrefix: string): string { + if (!val) return ''; + const re = new RegExp(`${labelPrefix}\\s*:\\s*(.+)`, 'i'); + const m = val.match(re); + return m ? m[1].trim() : ''; + } + + // Check if values have embedded labels and extract the correct fields + const rawNumInterior = rawDom.numeroInterior || ''; + const rawLocalidad = rawDom.localidad || ''; + + const colonia = rawDom.colonia + || extractEmbedded(rawNumInterior, 'Nombre de la Colonia') + || extractEmbedded(rawLocalidad, 'Nombre de la Colonia') + || ''; + const municipio = rawDom.municipio + || extractEmbedded(rawLocalidad, 'Nombre del Municipio o Demarcación Territorial') + || extractEmbedded(rawNumInterior, 'Nombre del Municipio') + || ''; + + // Map CSF field names → UI field names + const domicilioMapped = { + codigoPostal: cleanDomField(rawDom.codigoPostal), + calle: cleanDomField(rawDom.nombreVialidad) || '', + numExterior: cleanDomField(rawDom.numeroExterior), + numInterior: cleanDomField(rawDom.numeroInterior), + colonia: cleanDomField(colonia), + ciudad: cleanDomField(rawDom.localidad) || cleanDomField(rawDom.municipio) || '', + municipio: cleanDomField(municipio), + estado: cleanDomField(rawDom.entidadFederativa), + entreCalle: cleanDomField(rawDom.entreCalle), + yCalle: cleanDomField(rawDom.yCalle), + }; + + // Resolve ALL regímenes (not just the first) + let regimenClaves: string[] = []; + if (csf.regimenes?.length) { + const { prisma: centralPrisma } = await import('../config/database.js'); + const allRegimenes = await centralPrisma.regimen.findMany({ + where: { activo: true }, + select: { clave: true, descripcion: true }, + }); + + // Normalize for accent-insensitive comparison + const norm = (s: string) => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim(); + + for (const reg of csf.regimenes) { + if (reg.fechaFin) continue; // Skip inactive regimenes + const regNorm = norm(reg.nombre); + // Score-based: prefer the match with the highest overlap + let bestMatch: { clave: string; score: number } | null = null; + for (const r of allRegimenes) { + const catNorm = norm(r.descripcion); + // Exact match or containment + if (regNorm === catNorm || regNorm.includes(catNorm) || catNorm.includes(regNorm)) { + const score = catNorm.length; // Longer match = more specific = better + if (!bestMatch || score > bestMatch.score) { + bestMatch = { clave: r.clave, score }; + } + } + } + if (bestMatch) regimenClaves.push(bestMatch.clave); + } + } + + await pool.query(` + UPDATE contribuyentes SET + regimen_fiscal = COALESCE($2, regimen_fiscal), + codigo_postal = COALESCE($3, codigo_postal), + domicilio = COALESCE($4, domicilio) + WHERE entidad_id = $1 + `, [ + contribuyenteId, + regimenClaves.length > 0 ? regimenClaves.join(',') : null, + domicilioMapped.codigoPostal || null, + JSON.stringify(domicilioMapped), + ]); + console.log(`[CSF] Datos fiscales sincronizados para contribuyente ${contribuyenteId}: regímenes=${regimenClaves.join(',')}, CP=${domicilioMapped.codigoPostal}`); + } catch (syncErr: any) { + console.error(`[CSF] Error sincronizando datos fiscales:`, syncErr.message); + } + + return rowToConstancia(rows[0]); + })(); + + return await Promise.race([resultPromise, timeoutPromise]); + } finally { + await browser.close(); + } + } finally { + try { unlinkSync(cerPath); } catch { /* ok */ } + try { unlinkSync(keyPath); } catch { /* ok */ } + try { rmdirSync(tempDir); } catch { /* ok */ } + } +} diff --git a/apps/api/src/services/contribuyente-facturapi.service.ts b/apps/api/src/services/contribuyente-facturapi.service.ts new file mode 100644 index 0000000..f9dda97 --- /dev/null +++ b/apps/api/src/services/contribuyente-facturapi.service.ts @@ -0,0 +1,493 @@ +import Facturapi from 'facturapi'; +import type { Pool } from 'pg'; +import { Credential } from '@nodecfdi/credentials/node'; +import { env } from '../config/env.js'; +import { encryptString, decryptToString } from './sat/sat-crypto.service.js'; + +function getUserClient(): Facturapi { + if (!env.FACTURAPI_USER_KEY) throw new Error('FACTURAPI_USER_KEY no configurada'); + return new Facturapi(env.FACTURAPI_USER_KEY); +} + +/** + * Genera una Live Secret Key para una organización Facturapi via PUT idempotente. + * Si la org ya tiene live key, devuelve la existente; si no, crea una nueva. + * Endpoint oficial Facturapi: PUT /v2/organizations/{id}/apikeys/live + */ +async function generateLiveKey(orgId: string): Promise { + const userKey = env.FACTURAPI_USER_KEY!; + const res = await fetch(`https://www.facturapi.io/v2/organizations/${orgId}/apikeys/live`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${userKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }); + if (!res.ok) { + const errBody = await res.text().catch(() => ''); + throw new Error(`Facturapi PUT /apikeys/live falló (${res.status}): ${errBody}`); + } + const key = (await res.text()).replace(/"/g, '').trim(); + if (!key.startsWith('sk_live_')) { + throw new Error(`Respuesta inesperada de Facturapi (no es sk_live_*): ${key.slice(0, 10)}...`); + } + return key; +} + +/** + * Cifra y persiste la Live Secret Key de una organización. + * AES-256-GCM con la clave derivada de FIEL_ENCRYPTION_KEY. + */ +async function persistEncryptedKey(pool: Pool, orgId: string, plaintextKey: string): Promise { + const { encrypted, iv, tag } = encryptString(plaintextKey); + await pool.query( + `UPDATE facturapi_orgs SET api_key_enc = $1, api_key_iv = $2, api_key_tag = $3 WHERE facturapi_org_id = $4`, + [encrypted, iv, tag, orgId], + ); +} + +/** + * Obtiene la Live Secret Key cacheada (descifra de BD) o la genera vía PUT + * y la persiste si no existe (caso de orgs legacy creadas antes del refactor live). + */ +async function getOrgApiKey(pool: Pool, orgId: string): Promise { + const { rows } = await pool.query<{ api_key_enc: Buffer | null; api_key_iv: Buffer | null; api_key_tag: Buffer | null }>( + `SELECT api_key_enc, api_key_iv, api_key_tag FROM facturapi_orgs WHERE facturapi_org_id = $1 LIMIT 1`, + [orgId], + ); + if (rows.length === 0) throw new Error(`Organización ${orgId} no encontrada en BD tenant`); + + const row = rows[0]; + if (row.api_key_enc && row.api_key_iv && row.api_key_tag) { + return decryptToString(row.api_key_enc, row.api_key_iv, row.api_key_tag); + } + + // Org legacy sin live key cacheada — generar y guardar (idempotente). + const apiKey = await generateLiveKey(orgId); + await persistEncryptedKey(pool, orgId, apiKey); + return apiKey; +} + +export async function createOrgContribuyente( + pool: Pool, + contribuyenteId: string, + nombre: string +): Promise<{ orgId: string; reused?: boolean; recreated?: boolean }> { + const { rows: existing } = await pool.query( + 'SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id = $1', + [contribuyenteId] + ); + const client = getUserClient(); + + // Caso 1: hay fila local → verificar si la org sigue viva en Facturapi. + // Si existe en ambos lados, idempotente (devolver la existente). + // Si existe solo local pero Facturapi no la tiene (eliminada allá, API key + // cambió, etc.), recrear y actualizar el FK local — desbloquea el flujo + // de CSD que si no se quedaba trabado. + if (existing.length > 0) { + const existingId = existing[0].facturapi_org_id; + try { + await client.organizations.retrieve(existingId); + // Idempotente: si existe en ambos lados, asegurar que la live key está + // cacheada (puede faltar en orgs legacy creadas antes del refactor live). + await ensureLiveKeyCached(pool, existingId); + return { orgId: existingId, reused: true }; + } catch { + const org = await client.organizations.create({ name: nombre }); + await pool.query( + 'UPDATE facturapi_orgs SET facturapi_org_id = $2, csd_uploaded = false, active = true, api_key_enc = NULL, api_key_iv = NULL, api_key_tag = NULL WHERE contribuyente_id = $1', + [contribuyenteId, org.id] + ); + // Eager: generar y cachear live key para que la org quede lista para emitir. + await ensureLiveKeyCached(pool, org.id); + return { orgId: org.id, recreated: true }; + } + } + + // Caso 2: no hay fila local → crear fresh. + const org = await client.organizations.create({ name: nombre }); + await pool.query( + 'INSERT INTO facturapi_orgs (contribuyente_id, facturapi_org_id) VALUES ($1, $2)', + [contribuyenteId, org.id] + ); + // Eager: generar y cachear live key inmediatamente tras crear la org. + await ensureLiveKeyCached(pool, org.id); + return { orgId: org.id }; +} + +/** + * Garantiza que la org tiene su Live Secret Key cifrada en BD. Si ya existe, + * no-op. Si no, hace PUT live y la persiste. Idempotente — el endpoint + * Facturapi PUT /apikeys/live es idempotente, devuelve la misma key si ya + * existe en su lado. + */ +async function ensureLiveKeyCached(pool: Pool, orgId: string): Promise { + const { rows } = await pool.query( + `SELECT 1 FROM facturapi_orgs WHERE facturapi_org_id = $1 AND api_key_enc IS NOT NULL LIMIT 1`, + [orgId], + ); + if (rows.length > 0) return; + const apiKey = await generateLiveKey(orgId); + await persistEncryptedKey(pool, orgId, apiKey); +} + +export async function getOrgStatusContribuyente( + pool: Pool, + contribuyenteId: string +): Promise<{ configured: boolean; orgId?: string; legalName?: string; hasCsd?: boolean }> { + const { rows } = await pool.query( + 'SELECT facturapi_org_id, csd_uploaded FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true', + [contribuyenteId] + ); + if (rows.length === 0) return { configured: false }; + + try { + const client = getUserClient(); + const org = await client.organizations.retrieve(rows[0].facturapi_org_id); + return { + configured: true, + orgId: org.id, + legalName: org.legal?.name || undefined, + hasCsd: !!org.certificate?.has_certificate, + }; + } catch { + return { configured: false }; + } +} + +export async function uploadCsdContribuyente( + pool: Pool, + contribuyenteId: string, + cerFile: string, + keyFile: string, + password: string +): Promise<{ success: boolean; message: string }> { + const { rows } = await pool.query<{ facturapi_org_id: string; rfc: string }>( + `SELECT fo.facturapi_org_id, c.rfc + FROM facturapi_orgs fo + JOIN contribuyentes c ON c.entidad_id = fo.contribuyente_id + WHERE fo.contribuyente_id = $1 AND fo.active = true`, + [contribuyenteId] + ); + if (rows.length === 0) throw new Error('Primero debe crearse la organización Facturapi del contribuyente'); + + const { facturapi_org_id, rfc: contribuyenteRfc } = rows[0]; + + // Validación preventiva: que el certificado sea CSD (no FIEL), que el RFC + // coincida con el contribuyente y que no esté vencido. Facturapi también + // valida, pero su mensaje de error es poco específico ("Certificado no + // válido") — el nuestro dice exactamente qué pasa. + const cerData = Buffer.from(cerFile, 'base64'); + const keyData = Buffer.from(keyFile, 'base64'); + + let credential: Credential; + try { + credential = Credential.create(cerData.toString('binary'), keyData.toString('binary'), password); + } catch { + return { success: false, message: 'Los archivos .cer/.key no son válidos o la contraseña es incorrecta' }; + } + + // Debe ser CSD (sello digital para facturar), no FIEL (e.firma para trámites). + if (credential.isFiel()) { + return { success: false, message: 'El certificado es una FIEL (e.firma), no un CSD. Sube el Certificado de Sello Digital.' }; + } + + const certRfc = credential.certificate().rfc().toUpperCase(); + if (certRfc !== contribuyenteRfc.toUpperCase()) { + return { + success: false, + message: `El RFC del CSD (${certRfc}) no coincide con el del contribuyente (${contribuyenteRfc}). Verifica que estés subiendo los archivos correctos.`, + }; + } + + const validUntil = new Date(String(credential.certificate().validToDateTime())); + if (new Date() > validUntil) { + return { success: false, message: `El CSD está vencido desde ${validUntil.toLocaleDateString('es-MX')}. Solicita al SAT uno nuevo.` }; + } + + const client = getUserClient(); + try { + await client.organizations.uploadCertificate( + facturapi_org_id, + cerData, + keyData, + password, + ); + await pool.query( + 'UPDATE facturapi_orgs SET csd_uploaded = true WHERE contribuyente_id = $1', + [contribuyenteId] + ); + return { success: true, message: 'CSD subido correctamente' }; + } catch (error: any) { + return { success: false, message: error.message || 'Error al subir CSD a Facturapi' }; + } +} + +export async function getOrgClientContribuyente( + pool: Pool, + contribuyenteId: string +): Promise { + const { rows } = await pool.query( + 'SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true', + [contribuyenteId] + ); + if (rows.length === 0) throw new Error('Contribuyente no tiene organización Facturapi configurada'); + + const apiKey = await getOrgApiKey(pool, rows[0].facturapi_org_id); + return new Facturapi(apiKey); +} + +export async function cancelInvoiceContribuyente( + pool: Pool, + contribuyenteId: string, + facturapiId: string, + motive: '01' | '02' | '03' | '04' = '02', + substitution?: string, +): Promise { + const client = await getOrgClientContribuyente(pool, contribuyenteId); + const cancelData: any = { motive }; + if (motive === '01' && substitution) cancelData.substitution = substitution; + return client.invoices.cancel(facturapiId, cancelData); +} + +function streamToBuffer(stream: any): Promise { + return new Promise((resolve, reject) => { + if (Buffer.isBuffer(stream)) return resolve(stream); + const chunks: Buffer[] = []; + stream.on('data', (chunk: Buffer) => chunks.push(chunk)); + stream.on('end', () => resolve(Buffer.concat(chunks))); + stream.on('error', reject); + }); +} + +export async function downloadPdfContribuyente( + pool: Pool, + contribuyenteId: string, + facturapiId: string, +): Promise { + const client = await getOrgClientContribuyente(pool, contribuyenteId); + const stream = await client.invoices.downloadPdf(facturapiId); + return streamToBuffer(stream); +} + +export async function downloadXmlContribuyente( + pool: Pool, + contribuyenteId: string, + facturapiId: string, +): Promise { + const client = await getOrgClientContribuyente(pool, contribuyenteId); + const stream = await client.invoices.downloadXml(facturapiId); + return streamToBuffer(stream); +} + +export async function sendInvoiceByEmailContribuyente( + pool: Pool, + contribuyenteId: string, + facturapiId: string, + email: string, +): Promise { + const client = await getOrgClientContribuyente(pool, contribuyenteId); + await client.invoices.sendByEmail(facturapiId, { email }); +} + +export async function createInvoiceContribuyente( + pool: Pool, + contribuyenteId: string, + data: any +): Promise { + const client = await getOrgClientContribuyente(pool, contribuyenteId); + + // Create/update customer in Facturapi + const isForiegn = !!data.customer?.country && data.customer.country !== 'MEX'; + const customerData: any = { + legal_name: data.customer?.legalName, + tax_id: data.customer?.taxId, + email: data.customer?.email, + address: { zip: data.customer?.zip, ...(isForiegn ? { country: data.customer.country } : {}) }, + }; + if (!isForiegn && data.customer?.taxSystem) customerData.tax_system = data.customer.taxSystem; + + let customerId: string; + try { + const existing = await client.customers.list({ search: data.customer?.taxId }); + const match = existing.data?.find((c: any) => c.tax_id === data.customer?.taxId); + if (match) { + await client.customers.update(match.id, customerData); + customerId = match.id; + } else { + const created = await client.customers.create(customerData); + customerId = created.id; + } + } catch { + const created = await client.customers.create(customerData); + customerId = created.id; + } + + // Build invoice payload (mirrors createInvoice logic in facturapi.service.ts) + const tipo = data.type || 'I'; + const invoicePayload: any = { customer: customerId }; + + if (tipo !== 'I') invoicePayload.type = tipo; + + if (data.items?.length) { + invoicePayload.items = data.items.map((item: any) => ({ + quantity: item.quantity, + product: { + description: item.description, + product_key: item.productKey, + unit_key: item.unitKey || 'E48', + unit_name: item.unitName || 'Servicio', + price: item.price, + tax_included: item.taxIncluded ?? true, + taxes: item.taxes?.map((t: any) => ({ + type: t.type, + rate: t.rate, + factor: t.factor || 'Tasa', + ...(t.withholding ? { withholding: true } : {}), + })) || [{ type: 'IVA', rate: 0.16 }], + }, + })); + } + + if (tipo === 'I' || tipo === 'E') { + invoicePayload.use = data.use || 'G01'; + invoicePayload.payment_form = data.paymentForm || '99'; + invoicePayload.payment_method = data.paymentMethod || 'PUE'; + invoicePayload.currency = data.currency || 'MXN'; + if (data.exchangeRate && data.currency !== 'MXN') invoicePayload.exchange = data.exchangeRate; + if (data.conditions) invoicePayload.conditions = data.conditions; + } + + if (data.series) invoicePayload.series = data.series; + if (data.folioNumber) invoicePayload.folio_number = data.folioNumber; + + if (data.relatedDocuments?.length) { + // Estructura SAT 4.0: agrupa N uuids por tipo de relación. Acepta tanto + // el formato nuevo {relationship, uuids[]} como el legacy {relationship, + // uuid} para compat durante transición de callers frontend. + invoicePayload.related_documents = data.relatedDocuments.map((r: any) => ({ + relationship: r.relationship, + documents: Array.isArray(r.uuids) ? r.uuids : (r.uuid ? [r.uuid] : []), + })); + } + + if (data.complements?.length) invoicePayload.complements = data.complements; + if (data.global) invoicePayload.global = data.global; + + // Régimen fiscal del emisor: Facturapi NO acepta override per-invoice via + // campo `issuer` (rechaza con "issuer is not allowed"). La única forma es + // actualizar el `legal.tax_system` de la organización antes del emit. + // Para contribuyentes con múltiples regímenes, esto significa un sync en + // cada emit cuando el seleccionado difiere del actual en la org. + if (data.issuerTaxSystem) { + const { rows } = await pool.query<{ facturapi_org_id: string }>( + `SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true`, + [contribuyenteId], + ); + if (rows.length > 0) { + await ensureOrgLegalForEmit(pool, contribuyenteId, rows[0].facturapi_org_id, data.issuerTaxSystem); + } + } + + return client.invoices.create(invoicePayload); +} + +/** + * Sincroniza los datos fiscales de la organización Facturapi con la + * información del contribuyente, usando el régimen seleccionado. Se llama + * antes de cada emit cuando el user elige un régimen en el form, porque + * Facturapi toma el TaxSystem del CFDI del `legal.tax_system` de la org + * (no acepta override per-invoice). No-op si el `legal` ya coincide. + */ +async function ensureOrgLegalForEmit( + pool: Pool, + contribuyenteId: string, + orgId: string, + chosenTaxSystem: string, +): Promise { + const userKey = env.FACTURAPI_USER_KEY; + if (!userKey) throw new Error('FACTURAPI_USER_KEY no configurada'); + + // Datos fiscales del contribuyente (razón social + domicilio) + const { rows } = await pool.query<{ + rfc: string; + razon_social: string | null; + regimen_fiscal: string | null; + codigo_postal: string | null; + domicilio: any; + }>( + `SELECT c.rfc, r.razon_social, c.regimen_fiscal, c.codigo_postal, c.domicilio + FROM contribuyentes c + LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc) + WHERE c.entidad_id = $1`, + [contribuyenteId], + ); + if (rows.length === 0) throw new Error('Contribuyente no encontrado'); + const contrib = rows[0]; + + // Validar que el régimen elegido esté entre los registrados del contrib + const allowed = (contrib.regimen_fiscal || '') + .split(',') + .map(s => s.trim()) + .filter(Boolean); + if (allowed.length > 0 && !allowed.includes(chosenTaxSystem)) { + throw new Error( + `El régimen ${chosenTaxSystem} no está registrado para este contribuyente ` + + `(registrados: ${allowed.join(', ')})`, + ); + } + + // Leer el legal actual de la org en Facturapi + const getRes = await fetch(`https://www.facturapi.io/v2/organizations/${orgId}`, { + headers: { 'Authorization': `Bearer ${userKey}` }, + }); + if (!getRes.ok) { + throw new Error(`No se pudo leer organización Facturapi (${getRes.status})`); + } + const org = (await getRes.json()) as any; + const currentLegal = org.legal || {}; + + // Si el tax_system ya coincide y la razón social está seteada, no tocar + // (evita updates innecesarios con latencia extra). + if ( + currentLegal.tax_system === chosenTaxSystem && + currentLegal.legal_name && + currentLegal.legal_name === contrib.razon_social + ) { + return; + } + + const domicilio = (contrib.domicilio || {}) as any; + const legalPayload = { + name: contrib.razon_social || currentLegal.name || '', + legal_name: contrib.razon_social || currentLegal.legal_name || '', + tax_system: chosenTaxSystem, + address: { + street: domicilio.calle || currentLegal.address?.street || '', + exterior: domicilio.numExterior || currentLegal.address?.exterior || '', + interior: domicilio.numInterior || currentLegal.address?.interior || '', + neighborhood: domicilio.colonia || currentLegal.address?.neighborhood || '', + city: domicilio.ciudad || currentLegal.address?.city || '', + municipality: domicilio.municipio || currentLegal.address?.municipality || '', + state: domicilio.estado || currentLegal.address?.state || '', + zip: contrib.codigo_postal || domicilio.codigoPostal || currentLegal.address?.zip || '', + }, + }; + + const putRes = await fetch(`https://www.facturapi.io/v2/organizations/${orgId}/legal`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${userKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(legalPayload), + }); + + if (!putRes.ok) { + const errText = await putRes.text(); + throw new Error( + `Error actualizando datos fiscales de la organización Facturapi (${putRes.status}): ${errText}`, + ); + } +} diff --git a/apps/api/src/services/contribuyente-fiel.service.ts b/apps/api/src/services/contribuyente-fiel.service.ts new file mode 100644 index 0000000..0958e23 --- /dev/null +++ b/apps/api/src/services/contribuyente-fiel.service.ts @@ -0,0 +1,202 @@ +import { Credential } from '@nodecfdi/credentials/node'; +import type { Pool } from 'pg'; +import { encryptFielCredentials, decryptFielCredentials } from './sat/sat-crypto.service.js'; +import type { FielStatus } from '@horux/shared'; + +export async function uploadFielContribuyente( + pool: Pool, + contribuyenteId: string, + cerBase64: string, + keyBase64: string, + password: string +): Promise<{ success: boolean; message: string; status?: FielStatus }> { + try { + const cerData = Buffer.from(cerBase64, 'base64'); + const keyData = Buffer.from(keyBase64, 'base64'); + + let credential: Credential; + try { + credential = Credential.create(cerData.toString('binary'), keyData.toString('binary'), password); + } catch { + return { success: false, message: 'Los archivos de la FIEL no son válidos o la contraseña es incorrecta' }; + } + + if (!credential.isFiel()) { + return { success: false, message: 'El certificado proporcionado no es una FIEL (e.firma). Parece ser un CSD.' }; + } + + const certificate = credential.certificate(); + const rfc = certificate.rfc(); + const serialNumber = certificate.serialNumber().bytes(); + const validFrom = new Date(String(certificate.validFromDateTime())); + const validUntil = new Date(String(certificate.validToDateTime())); + + if (new Date() > validUntil) { + return { success: false, message: 'La FIEL está vencida desde ' + validUntil.toLocaleDateString() }; + } + + const enc = encryptFielCredentials(cerData, keyData, password); + + // Check whether this contribuyente already had an active FIEL (to decide auto-sync) + const { rows: existingRows } = await pool.query( + `SELECT 1 FROM fiel_contribuyente WHERE contribuyente_id = $1 AND is_active = true`, + [contribuyenteId] + ); + const isFirstUpload = existingRows.length === 0; + + await pool.query(` + INSERT INTO fiel_contribuyente ( + contribuyente_id, rfc, cer_data, key_data, key_password_enc, + cer_iv, cer_tag, key_iv, key_tag, password_iv, password_tag, + serial_number, valid_from, valid_until, is_active + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, true) + ON CONFLICT (contribuyente_id) DO UPDATE SET + rfc = $2, cer_data = $3, key_data = $4, key_password_enc = $5, + cer_iv = $6, cer_tag = $7, key_iv = $8, key_tag = $9, + password_iv = $10, password_tag = $11, + serial_number = $12, valid_from = $13, valid_until = $14, + is_active = true, updated_at = now() + `, [ + contribuyenteId, rfc, + enc.encryptedCer, enc.encryptedKey, enc.encryptedPassword, + enc.cerIv, enc.cerTag, enc.keyIv, enc.keyTag, enc.passwordIv, enc.passwordTag, + serialNumber, validFrom, validUntil, + ]); + + // Trigger auto-sync on first upload (fire-and-forget) + if (isFirstUpload) { + import('./opinion-cumplimiento.service.js').then(async ({ consultarOpinionContribuyente }) => { + try { + await consultarOpinionContribuyente(pool, contribuyenteId); + } catch (err: any) { + console.error(`[FIEL first-upload] Opinión falló para contribuyente ${contribuyenteId}:`, err.message || err); + } + }).catch(() => {}); + + import('./constancia.service.js').then(async ({ consultarConstanciaContribuyente }) => { + try { + await consultarConstanciaContribuyente(pool, contribuyenteId); + } catch (err: any) { + console.error(`[FIEL first-upload] CSF falló para contribuyente ${contribuyenteId}:`, err.message || err); + } + }).catch(() => {}); + } + + const daysUntilExpiration = Math.ceil((validUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24)); + + return { + success: true, + message: 'FIEL configurada correctamente', + status: { configured: true, rfc, serialNumber, validFrom: validFrom.toISOString(), validUntil: validUntil.toISOString(), isExpired: false, daysUntilExpiration }, + }; + } catch (error: any) { + console.error('[FIEL Contribuyente Upload Error]', error); + return { success: false, message: error.message || 'Error al procesar la FIEL' }; + } +} + +export async function getFielStatusContribuyente(pool: Pool, contribuyenteId: string): Promise { + // Try per-contribuyente first (tenant BD) + const { rows } = await pool.query(` + SELECT rfc, serial_number AS "serialNumber", valid_from AS "validFrom", valid_until AS "validUntil", is_active AS "isActive" + FROM fiel_contribuyente WHERE contribuyente_id = $1 + `, [contribuyenteId]); + + if (rows.length === 0 || !rows[0].isActive) { + // Fallback: check legacy tenant-level FIEL by matching RFC + const { rows: contribRows } = await pool.query('SELECT rfc FROM contribuyentes WHERE entidad_id = $1', [contribuyenteId]); + const rfc = contribRows[0]?.rfc; + if (rfc) { + const { getFielStatus } = await import('./fiel.service.js'); + // getFielStatus reads by tenantId — check if the legacy FIEL matches this RFC + // We need prisma access, so import it + const { prisma } = await import('../config/database.js'); + const legacyFiel = await prisma.fielCredential.findFirst({ + where: { rfc, isActive: true }, + select: { rfc: true, serialNumber: true, validFrom: true, validUntil: true, isActive: true }, + }); + if (legacyFiel) { + const now = new Date(); + const isExpired = now > legacyFiel.validUntil; + const daysUntilExpiration = Math.ceil((legacyFiel.validUntil.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); + return { + configured: true, + rfc: legacyFiel.rfc, + serialNumber: legacyFiel.serialNumber || undefined, + validFrom: legacyFiel.validFrom.toISOString(), + validUntil: legacyFiel.validUntil.toISOString(), + isExpired, + daysUntilExpiration: isExpired ? 0 : daysUntilExpiration, + }; + } + } + return { configured: false }; + } + + const fiel = rows[0]; + const now = new Date(); + const isExpired = now > new Date(fiel.validUntil); + const daysUntilExpiration = Math.ceil((new Date(fiel.validUntil).getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); + + return { + configured: true, + rfc: fiel.rfc, + serialNumber: fiel.serialNumber || undefined, + validFrom: new Date(fiel.validFrom).toISOString(), + validUntil: new Date(fiel.validUntil).toISOString(), + isExpired, + daysUntilExpiration: isExpired ? 0 : daysUntilExpiration, + }; +} + +export async function getDecryptedFielContribuyente(pool: Pool, contribuyenteId: string): Promise<{ + cerContent: string; keyContent: string; password: string; rfc: string; +} | null> { + const { rows } = await pool.query(` + SELECT * FROM fiel_contribuyente WHERE contribuyente_id = $1 AND is_active = true + `, [contribuyenteId]); + + if (rows.length === 0) { + // Fallback: check legacy FIEL by matching RFC + const { rows: contribRows } = await pool.query('SELECT rfc FROM contribuyentes WHERE entidad_id = $1', [contribuyenteId]); + const rfc = contribRows[0]?.rfc; + if (rfc) { + const { prisma } = await import('../config/database.js'); + const legacyFiel = await prisma.fielCredential.findFirst({ + where: { rfc, isActive: true }, + }); + if (legacyFiel && new Date() <= legacyFiel.validUntil) { + try { + const { decryptFielCredentials } = await import('./sat/sat-crypto.service.js'); + const { cerData, keyData, password } = decryptFielCredentials( + Buffer.from(legacyFiel.cerData), Buffer.from(legacyFiel.keyData), Buffer.from(legacyFiel.keyPasswordEncrypted), + Buffer.from(legacyFiel.cerIv), Buffer.from(legacyFiel.cerTag), + Buffer.from(legacyFiel.keyIv), Buffer.from(legacyFiel.keyTag), + Buffer.from(legacyFiel.passwordIv), Buffer.from(legacyFiel.passwordTag) + ); + return { cerContent: cerData.toString('binary'), keyContent: keyData.toString('binary'), password, rfc: legacyFiel.rfc }; + } catch (err) { + console.error('[FIEL Contribuyente] Legacy decrypt failed:', err); + return null; + } + } + } + return null; + } + const fiel = rows[0]; + + if (new Date() > new Date(fiel.valid_until)) return null; + + try { + const { cerData, keyData, password } = decryptFielCredentials( + Buffer.from(fiel.cer_data), Buffer.from(fiel.key_data), Buffer.from(fiel.key_password_enc), + Buffer.from(fiel.cer_iv), Buffer.from(fiel.cer_tag), + Buffer.from(fiel.key_iv), Buffer.from(fiel.key_tag), + Buffer.from(fiel.password_iv), Buffer.from(fiel.password_tag) + ); + return { cerContent: cerData.toString('binary'), keyContent: keyData.toString('binary'), password, rfc: fiel.rfc }; + } catch (error) { + console.error('[FIEL Contribuyente Decrypt Error]', error); + return null; + } +} diff --git a/apps/api/src/services/contribuyente.service.ts b/apps/api/src/services/contribuyente.service.ts new file mode 100644 index 0000000..251804e --- /dev/null +++ b/apps/api/src/services/contribuyente.service.ts @@ -0,0 +1,187 @@ +import type { Pool } from 'pg'; + +export interface CreateContribuyenteData { + rfc: string; + razonSocial: string; + regimenFiscal?: string; + codigoPostal?: string; + domicilio?: Record; + supervisorUserId?: string; +} + +export interface ContribuyenteRow { + id: string; + tipo: string; + nombre: string; + identificador: string; + supervisorUserId: string | null; + active: boolean; + createdAt: string; + rfc: string; + regimenFiscal: string | null; + codigoPostal: string | null; + domicilio: Record | null; +} + +export async function listContribuyentes(pool: Pool, entidadIds?: string[]): Promise { + let query = ` + SELECT + e.id, e.tipo, e.nombre, e.identificador, + e.supervisor_user_id AS "supervisorUserId", + e.active, e.created_at AS "createdAt", + c.rfc, c.regimen_fiscal AS "regimenFiscal", + c.codigo_postal AS "codigoPostal", c.domicilio + FROM entidades_gestionadas e + JOIN contribuyentes c ON c.entidad_id = e.id + WHERE e.active = true + `; + const params: unknown[] = []; + + if (entidadIds !== undefined) { + if (entidadIds.length === 0) return []; // No access = empty list + query += ` AND e.id = ANY($1)`; + params.push(entidadIds); + } + + query += ' ORDER BY e.created_at DESC'; + const { rows } = await pool.query(query, params); + return rows; +} + +export async function getContribuyenteById(pool: Pool, id: string): Promise { + const { rows } = await pool.query(` + SELECT + e.id, e.tipo, e.nombre, e.identificador, + e.supervisor_user_id AS "supervisorUserId", + e.active, e.created_at AS "createdAt", + c.rfc, c.regimen_fiscal AS "regimenFiscal", + c.codigo_postal AS "codigoPostal", c.domicilio + FROM entidades_gestionadas e + JOIN contribuyentes c ON c.entidad_id = e.id + WHERE e.id = $1 + `, [id]); + return rows[0] ?? null; +} + +export async function createContribuyente(pool: Pool, data: CreateContribuyenteData): Promise { + const client = await pool.connect(); + try { + await client.query('BEGIN'); + const { rows: [entidad] } = await client.query(` + INSERT INTO entidades_gestionadas (tipo, nombre, identificador, supervisor_user_id) + VALUES ('CONTRIBUYENTE', $1, $2, $3) + RETURNING id + `, [data.razonSocial, data.rfc.toUpperCase(), data.supervisorUserId ?? null]); + + await client.query(` + INSERT INTO contribuyentes (entidad_id, rfc, regimen_fiscal, codigo_postal, domicilio) + VALUES ($1, $2, $3, $4, $5) + `, [entidad.id, data.rfc.toUpperCase(), data.regimenFiscal ?? null, data.codigoPostal ?? null, data.domicilio ? JSON.stringify(data.domicilio) : null]); + + await client.query('COMMIT'); + + // Backfill: claim existing CFDIs that match this RFC + await backfillCfdiContribuyente(pool, entidad.id, data.rfc.toUpperCase()).catch( + (err) => console.error('[Contribuyente] Backfill CFDIs failed (non-blocking):', err) + ); + + return (await getContribuyenteById(pool, entidad.id))!; + } catch (err) { + await client.query('ROLLBACK'); + throw err; + } finally { + client.release(); + } +} + +export async function updateContribuyente(pool: Pool, id: string, data: Partial): Promise { + const existing = await getContribuyenteById(pool, id); + if (!existing) return null; + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + // Update entidades_gestionadas if needed + const entidadSets: string[] = []; + const entidadVals: unknown[] = []; + let idx = 1; + + if (data.razonSocial) { + entidadSets.push(`nombre = $${idx}`, `identificador = $${idx}`); + entidadVals.push(data.razonSocial); + idx++; + } + if (data.supervisorUserId !== undefined) { + entidadSets.push(`supervisor_user_id = $${idx}`); + entidadVals.push(data.supervisorUserId); + idx++; + } + if (entidadSets.length > 0) { + entidadSets.push('updated_at = now()'); + entidadVals.push(id); + await client.query(`UPDATE entidades_gestionadas SET ${entidadSets.join(', ')} WHERE id = $${idx}`, entidadVals); + } + + // Update contribuyentes if needed + const contribSets: string[] = []; + const contribVals: unknown[] = []; + idx = 1; + + if (data.regimenFiscal !== undefined) { contribSets.push(`regimen_fiscal = $${idx}`); contribVals.push(data.regimenFiscal); idx++; } + if (data.codigoPostal !== undefined) { contribSets.push(`codigo_postal = $${idx}`); contribVals.push(data.codigoPostal); idx++; } + if (data.domicilio !== undefined) { contribSets.push(`domicilio = $${idx}`); contribVals.push(JSON.stringify(data.domicilio)); idx++; } + + if (contribSets.length > 0) { + contribVals.push(id); + await client.query(`UPDATE contribuyentes SET ${contribSets.join(', ')} WHERE entidad_id = $${idx}`, contribVals); + } + + await client.query('COMMIT'); + return (await getContribuyenteById(pool, id))!; + } catch (err) { + await client.query('ROLLBACK'); + throw err; + } finally { + client.release(); + } +} + +export async function deactivateContribuyente(pool: Pool, id: string): Promise { + const { rowCount } = await pool.query( + 'UPDATE entidades_gestionadas SET active = false, updated_at = now() WHERE id = $1', + [id] + ); + return (rowCount ?? 0) > 0; +} + +/** + * Assigns contribuyente_id to CFDIs that match the RFC (emisor or receptor). + * Runs after contribuyente creation and can be called manually for backfill. + * Only updates CFDIs where contribuyente_id IS NULL (doesn't override). + */ +export async function backfillCfdiContribuyente(pool: Pool, contribuyenteId: string, rfc: string): Promise { + const { rowCount } = await pool.query(` + UPDATE cfdis + SET contribuyente_id = $1 + WHERE contribuyente_id IS NULL + AND (rfc_emisor = $2 OR rfc_receptor = $2) + `, [contribuyenteId, rfc]); + const count = rowCount ?? 0; + if (count > 0) { + console.log(`[Backfill] Assigned ${count} CFDIs to contribuyente ${rfc} (${contribuyenteId})`); + } + return count; +} + +/** + * Backfills ALL contribuyentes in the tenant BD. Useful after initial SAT sync. + */ +export async function backfillAllContribuyentes(pool: Pool): Promise { + const { rows } = await pool.query('SELECT entidad_id, rfc FROM contribuyentes'); + let total = 0; + for (const { entidad_id, rfc } of rows) { + total += await backfillCfdiContribuyente(pool, entidad_id, rfc); + } + return total; +} diff --git a/apps/api/src/services/dashboard.service.ts b/apps/api/src/services/dashboard.service.ts new file mode 100644 index 0000000..37a3e2b --- /dev/null +++ b/apps/api/src/services/dashboard.service.ts @@ -0,0 +1,1253 @@ +import type { Pool } from 'pg'; +import type { KpiData, IngresoRegimen, EgresoRegimen, IvaBalanceRegimen, IngresosEgresosData, ResumenFiscal, Alerta } from '@horux/shared'; +import { getRegimenesIgnoradosClaves } from './regimen.service.js'; +import { prisma } from '../config/database.js'; +import { planCache, type CacheRange } from '../utils/metricas-cache.js'; +import { resolveContribuyenteContext } from '../utils/contribuyente-context.js'; +import { buildExtraFilters } from './_shared/cfdi-filters.js'; + +// Status vigente +const VIGENTE = `status NOT IN ('Cancelado', '0')`; + +// Impuestos trasladados del comprobante +const IMP_TRAS = `COALESCE(iva_traslado_mxn,0) + COALESCE(ieps_traslado_mxn,0) + COALESCE(impuestos_locales_trasladado_mxn,0)`; + +// Impuestos trasladados del pago. +// El IVA se clampa a `monto_pago_mxn × 0.16` (tasa SAT máxima) como defensa +// contra XMLs malformados donde el proveedor reporta el IVA de la factura +// original completa en vez del proporcional al pago parcial. Caso real: +// CFDI 079ace7d con monto_pago=$43,611 e iva_traslado_pago=$30,076 cuando +// el proporcional sería ~$6,017. IEPS NO se clampa (rates van hasta 53%). +const IVA_TRAS_PAGO_CLAMPED = `LEAST(COALESCE(iva_traslado_pago_mxn, 0), COALESCE(monto_pago_mxn, 0) * 0.16)`; +const IVA_RET_PAGO_CLAMPED = `LEAST(COALESCE(iva_retencion_pago_mxn, 0), COALESCE(monto_pago_mxn, 0) * 0.16)`; +const IMP_TRAS_PAGO = `${IVA_TRAS_PAGO_CLAMPED} + COALESCE(ieps_traslado_pago_mxn, 0)`; + +// Claves de producto/servicio excluidas de cálculos fiscales +const CLAVES_EXCLUIDAS = `('84121603','93161608','85101501','85121800')`; + +// Art. 27 fracción III LISR — gastos > $2,000 pagados en efectivo NO son +// deducibles. Aplica al lado RECEPTOR. Se filtran de las deducciones y se +// surface aparte en card "No Deducibles". +// +// Para CFDIs tipo I PUE: el monto a comparar es `total_mxn`. +// Para complementos P: el monto a comparar es `monto_pago_mxn` (cada P es +// pago independiente; un P de $3k efectivo aplicado a una I PPD bloquea +// solo esos $3k, no la I PPD entera). +// +// `forma_pago = '01'` = Efectivo (catálogo SAT c_FormaPago). +// +// EXPORTADOS — `impuestos.service.ts` los reutiliza para excluir el IVA +// acreditable de esos mismos gastos (Art. 5 LIVA fracción I: el IVA solo es +// acreditable si el gasto cumple los requisitos de deducibilidad ISR). +// +// IMPORTANTE: COALESCE(forma_pago, '') hace el predicado NULL-safe. Sin esto, +// `forma_pago = '01'` retorna NULL cuando forma_pago es NULL, y `NOT (NULL)` +// también es NULL — Postgres trata WHERE NULL como exclusión del row. Eso +// haría que TODOS los CFDIs sin forma_pago se excluyeran de las deducciones +// vía `AND NOT NO_DEDUCIBLE_EFECTIVO_*`. Los CFDIs de complemento P sin +// forma_pago explícito son comunes; sin el COALESCE, deducciones colapsa. +export const NO_DEDUCIBLE_EFECTIVO_I_PUE = `(COALESCE(forma_pago, '') = '01' AND COALESCE(total_mxn, 0) > 2000)`; +export const NO_DEDUCIBLE_EFECTIVO_P = `(COALESCE(forma_pago, '') = '01' AND COALESCE(monto_pago_mxn, 0) > 2000)`; + +// Subtotal de conceptos excluidos por CFDI (importe - descuento) +const EXCL_MONTO = `COALESCE((SELECT SUM(COALESCE(cc.importe_mxn,0) - COALESCE(cc.descuento_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; + +// IVA trasladado de conceptos excluidos +const EXCL_IVA_TRAS = `COALESCE((SELECT SUM(COALESCE(cc.iva_traslado_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; + +// IVA retenido de conceptos excluidos +const EXCL_IVA_RET = `COALESCE((SELECT SUM(COALESCE(cc.iva_retencion_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; + +// IVA neto excluido (trasladado - retenido) +const EXCL_IVA_NETO = `(${EXCL_IVA_TRAS}) - (${EXCL_IVA_RET})`; + +// "Total sin impuestos" custom usado para I/07 (aplicación de anticipo) en +// Grupo 1. Fórmula: total − traslados + retenciones. Semánticamente +// representa la base gravable del anticipo considerando que las retenciones +// sí son ingreso (aunque el retenedor se las haya quedado). +const NETO_CUSTOM = (alias: string) => `( + COALESCE(${alias}.total_mxn, 0) + - COALESCE(${alias}.iva_traslado_mxn, 0) + COALESCE(${alias}.iva_retencion_mxn, 0) + + COALESCE(${alias}.isr_retencion_mxn, 0) + - COALESCE(${alias}.ieps_traslado_mxn, 0) + COALESCE(${alias}.ieps_retencion_mxn, 0) + - COALESCE(${alias}.impuestos_locales_trasladado_mxn, 0) + COALESCE(${alias}.impuestos_locales_retenidos_mxn, 0) +)`; + +// EXCL_MONTO parametrizado por alias — para aplicarlo tanto al CFDI base +// como a las facturas relacionadas dentro de un subquery. +const EXCL_MONTO_ALIAS = (alias: string) => `COALESCE(( + SELECT SUM(COALESCE(cc.importe_mxn, 0) - COALESCE(cc.descuento_mxn, 0)) + FROM cfdi_conceptos cc + WHERE cc.cfdi_id = ${alias}.id + AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS} +), 0)`; + +// EXCL_IVA parametrizado por alias (para compensación I/07 en IVA). +const EXCL_IVA_TRAS_ALIAS = (alias: string) => `COALESCE(( + SELECT SUM(COALESCE(cc.iva_traslado_mxn, 0)) + FROM cfdi_conceptos cc + WHERE cc.cfdi_id = ${alias}.id + AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS} +), 0)`; +const EXCL_IVA_RET_ALIAS = (alias: string) => `COALESCE(( + SELECT SUM(COALESCE(cc.iva_retencion_mxn, 0)) + FROM cfdi_conceptos cc + WHERE cc.cfdi_id = ${alias}.id + AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS} +), 0)`; +const EXCL_IVA_NETO_ALIAS = (alias: string) => + `((${EXCL_IVA_TRAS_ALIAS(alias)}) - (${EXCL_IVA_RET_ALIAS(alias)}))`; + +// IVA neto por fila, parametrizado por alias (iva_traslado - iva_retencion). +const IVA_NETO_ALIAS = (alias: string) => + `(COALESCE(${alias}.iva_traslado_mxn, 0) - COALESCE(${alias}.iva_retencion_mxn, 0))`; + +// Grupos de regímenes por lógica de cálculo +export const GRUPO_PF_EMPRESARIAL = ['606', '612', '621', '625', '626']; +export const GRUPO_SUELDOS = ['605']; +export const GRUPO_PM_OTROS = ['601', '603', '607', '608', '610', '611', '614', '615', '620', '622', '623', '624']; +const TODOS_REGIMENES = [...GRUPO_PF_EMPRESARIAL, ...GRUPO_SUELDOS, ...GRUPO_PM_OTROS]; + +// Filtro de fecha por rango — normal o conciliación +const FECHA_RANGO = `fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day')`; +// Para CFDIs tipo P (complementos de pago): el ingreso/gasto se reconoce en la +// fecha_pago_p (cuándo el cliente realmente pagó), no cuando se emitió el +// complemento — el CFDI P puede emitirse hasta el día 5 del mes siguiente al +// pago, o incluso después, y cruzar meses (ej. pago de noviembre 2024 con +// complemento emitido en mayo 2025). +const FECHA_PAGO_RANGO = `fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')`; +const FECHA_RANGO_CONCILIACION = `id_conciliacion IS NOT NULL AND id_conciliacion IN ( + SELECT id FROM conciliaciones WHERE fecha_de_pago >= $1::date AND fecha_de_pago < ($2::date + interval '1 day') +)`; + +function getFechaRango(conciliacion?: boolean): string { + return conciliacion ? FECHA_RANGO_CONCILIACION : FECHA_RANGO; +} + +/** Igual que getFechaRango pero para CFDIs tipo P: filtra por fecha_pago_p. */ +function getFechaPagoRango(conciliacion?: boolean): string { + return conciliacion ? FECHA_RANGO_CONCILIACION : FECHA_PAGO_RANGO; +} + +/** + * Filtro de contribuyente **inclusivo**: matchea por `contribuyente_id` + * directo O por RFC en cualquiera de los dos lados (emisor/receptor). + * + * Necesario porque cuando dos contribuyentes del mismo tenant tienen + * relación emisor-receptor, el sync SAT del primero inserta el CFDI con + * su `contribuyente_id` y el sync del segundo (UPSERT) NO actualiza el + * campo. Resultado: el CFDI queda asignado al primer contribuyente aunque + * desde la perspectiva del segundo es "su" CFDI con el `type` inverso. + * + * Solución: en vez de filtrar `contribuyente_id = X`, filtrar por RFC del + * contribuyente en ambos lados. El `type` del CFDI + el lado del query + * (EMITIDO vs RECIBIDO) ya determina si es ingreso o gasto de ese + * contribuyente — no requiere `contribuyente_id` para la correcta + * atribución. + * + * Devuelve fragmento SQL con `AND` prefijo; string vacío si no hay + * `contribuyenteId` provisto. + */ +async function getContribFilter(pool: Pool, contribuyenteId: string | null | undefined): Promise { + if (!contribuyenteId) return ''; + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + if (!safeId) return ''; + + const { rows } = await pool.query<{ rfc: string | null }>( + `SELECT rfc FROM contribuyentes WHERE entidad_id = $1`, + [safeId], + ); + if (rows.length === 0 || !rows[0].rfc) { + // Fallback: solo contribuyente_id (contribuyente sin RFC registrado). + return `AND contribuyente_id = '${safeId}'`; + } + const rfc = rows[0].rfc.replace(/[^A-Z0-9]/gi, '').toUpperCase(); + return `AND (contribuyente_id = '${safeId}' OR UPPER(rfc_emisor) = '${rfc}' OR UPPER(rfc_receptor) = '${rfc}')`; +} + +/** + * Calcula "Ingresos del Mes" desglosados por régimen fiscal. + */ +async function getDescMap(cache?: Map): Promise> { + if (cache) return cache; + const catalogo = await prisma.regimen.findMany({ where: { activo: true } }); + return new Map(catalogo.map(r => [r.clave, r.descripcion])); +} + +async function getIgnorados(tenantId: string, cache?: string[]): Promise { + if (cache) return cache; + return getRegimenesIgnoradosClaves(tenantId); +} + +// ──────────────────────────────────────────────────────────────────── +// Read-through cache (Tanda B hot/cold) +// +// Para contribuyentes con datos en `metricas_mensuales`, un rango que cubre +// meses completos dentro de años pasados (previos al actual) se puede leer +// directo de la tabla en vez de recomputar desde raw CFDIs. El año actual y +// los rangos parciales siguen on-the-fly. +// +// Requisitos para usar cache: +// - `contribuyenteId` presente (sin él no hay filas en la tabla) +// - `conciliacion` desactivada (la tabla guarda flujo normal, no usa id_conciliacion) +// - `fechaFin` antes del primer día del año actual +// - `fechaInicio` es día 1 del mes, `fechaFin` es último día del mes (rango +// de meses completos; parciales no mapean a filas de la tabla) +// ──────────────────────────────────────────────────────────────────── + +/** Lee ingresos_cobrados agregados por régimen desde metricas_mensuales. */ +async function readIngresosFromCache( + pool: Pool, + range: CacheRange, + ignorados: string[], + descMap: Map, +): Promise<{ total: number; porRegimen: IngresoRegimen[] } | null> { + const { rows } = await pool.query<{ regimen: string; monto: string; rows_n: string }>(` + SELECT regimen_fiscal AS regimen, + COALESCE(SUM(ingresos_cobrados), 0)::numeric(14,2) AS monto, + COUNT(*) AS rows_n + FROM metricas_mensuales + WHERE contribuyente_id = $1 + AND make_date(anio, mes, 1) BETWEEN $2::date AND $3::date + AND regimen_fiscal IS NOT NULL + GROUP BY regimen_fiscal + `, [range.contribuyenteId, range.startDate, range.endDate]); + + // Si no hay filas cacheadas, señal al caller de hacer fallback on-the-fly. + if (rows.length === 0) return null; + + const porRegimen: IngresoRegimen[] = []; + for (const r of rows) { + if (ignorados.includes(r.regimen)) continue; + const monto = Number(r.monto); + if (monto !== 0) { + porRegimen.push({ + regimenClave: r.regimen, + regimenDescripcion: descMap.get(r.regimen) || r.regimen, + monto, + }); + } + } + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** Lee egresos_pagados agregados por régimen desde metricas_mensuales. */ +async function readEgresosFromCache( + pool: Pool, + range: CacheRange, + ignorados: string[], + descMap: Map, +): Promise<{ total: number; porRegimen: EgresoRegimen[] } | null> { + const { rows } = await pool.query<{ regimen: string; monto: string }>(` + SELECT regimen_fiscal AS regimen, + COALESCE(SUM(egresos_pagados), 0)::numeric(14,2) AS monto + FROM metricas_mensuales + WHERE contribuyente_id = $1 + AND make_date(anio, mes, 1) BETWEEN $2::date AND $3::date + AND regimen_fiscal IS NOT NULL + GROUP BY regimen_fiscal + `, [range.contribuyenteId, range.startDate, range.endDate]); + + if (rows.length === 0) return null; + + const porRegimen: EgresoRegimen[] = []; + for (const r of rows) { + if (ignorados.includes(r.regimen)) continue; + const monto = Number(r.monto); + if (monto !== 0) { + porRegimen.push({ + regimenClave: r.regimen, + regimenDescripcion: descMap.get(r.regimen) || r.regimen, + monto, + }); + } + } + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Lee IVA balance por régimen desde metricas_mensuales. + * Fórmula: monto = iva_trasladado_total − iva_acreditable − iva_retenido_cobrado + * (alineada con impuestos.resultado tras el refactor que separó retención). + */ +async function readIvaBalanceFromCache( + pool: Pool, + range: CacheRange, + ignorados: string[], + descMap: Map, +): Promise<{ total: number; porRegimen: IvaBalanceRegimen[] } | null> { + const { rows } = await pool.query<{ regimen: string; causado: string; acreditable: string; retenido: string }>(` + SELECT regimen_fiscal AS regimen, + COALESCE(SUM(iva_trasladado_total), 0)::numeric(14,2) AS causado, + COALESCE(SUM(iva_acreditable), 0)::numeric(14,2) AS acreditable, + COALESCE(SUM(iva_retenido_cobrado), 0)::numeric(14,2) AS retenido + FROM metricas_mensuales + WHERE contribuyente_id = $1 + AND make_date(anio, mes, 1) BETWEEN $2::date AND $3::date + AND regimen_fiscal IS NOT NULL + GROUP BY regimen_fiscal + `, [range.contribuyenteId, range.startDate, range.endDate]); + + if (rows.length === 0) return null; + + const porRegimen: IvaBalanceRegimen[] = []; + for (const r of rows) { + if (ignorados.includes(r.regimen)) continue; + const causado = Number(r.causado); + const acreditable = Number(r.acreditable); + const retenido = Number(r.retenido); + const monto = causado - acreditable - retenido; + if (monto !== 0 || causado !== 0 || acreditable !== 0 || retenido !== 0) { + porRegimen.push({ + regimenClave: r.regimen, + regimenDescripcion: descMap.get(r.regimen) || r.regimen, + monto, + }); + } + } + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +export async function calcularIngresosPorRegimen( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + _ignorados?: string[], + _descMap?: Map, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise<{ total: number; porRegimen: IngresoRegimen[] }> { + const ignorados = await getIgnorados(tenantId, _ignorados); + const descMap = await getDescMap(_descMap); + + // Read-through cache: si el rango cae en años pasados con meses completos + // y hay un contribuyente seleccionado, lee de metricas_mensuales. Si hit, + // retorna de inmediato (evita ~3 queries SQL por régimen). Solo aplica + // cuando los toggles están en default (true) — el cache se escribió con + // esos valores y aplicar otra combinación devolvería datos stale. + const cacheRange = considerarActivos && considerarNCs + ? planCache(fechaInicio, fechaFin, conciliacion, contribuyenteId) + : null; + if (cacheRange) { + const cached = await readIngresosFromCache(pool, cacheRange, ignorados, descMap); + if (cached) return cached; + } + + const FR = getFechaRango(conciliacion); + const FR_PAGO = getFechaPagoRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esEmisor = ctx.esEmisor; + const esReceptor = ctx.esReceptor; + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + const porRegimen: IngresoRegimen[] = []; + + // ─── GRUPO 1: PF Empresarial (606, 612, 621, 625, 626) ─── + // Suman I PUE + P (pagos) + I/07 PPD compensación. Las notas de crédito tipo E + // se contabilizan del lado del receptor (gastos) y se exhiben aparte como + // "Egresos Emitidos" en /impuestos > ISR (surface-only) — no restan aquí. + // + // I/07 PPD compensación (lado EMISOR): cuando el contribuyente emite I/07 PPD + // (aplicación de anticipo) y emite también una E en el mismo mes/año cuya + // cfdis_relacionados contiene esa I/07 PPD, la I/07 PPD aporta el equivalente + // de la base de la E. Se preserva por interpretación fiscal explícita aunque + // la E ya no se reste — refleja que la porción del servicio asociada al + // anticipo se reconoce como ingreso al emitir la I/07 PPD. + // + // Filtro por RFC del emisor (`${esEmisor}`) en vez de `type='EMITIDO' AND + // contribuyente_id=X` — el RFC es fuente de verdad, type/contribuyente_id + // pueden ser inconsistentes cuando dos contribuyentes del tenant se facturan. + const { rows: g1Facturas } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(COALESCE(total_mxn, 0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR}${extra} + AND regimen_fiscal_emisor = ANY($3) + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin, GRUPO_PF_EMPRESARIAL]); + + const { rows: g1Pagos } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO})), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${FR_PAGO}${extra} + AND regimen_fiscal_emisor = ANY($3) + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin, GRUPO_PF_EMPRESARIAL]); + + // NOTA: la compensación I/07 PPD ↔ E se eliminó por decisión del cliente + // (2026-05-02). No es un cálculo oficial del SAT y confundía a contadores — + // el contador hace la conciliación manual del ciclo anticipo→I/07 PPD→E si + // aplica. Mantener esta nota para evitar reintroducirla por intuición fiscal. + for (const clave of GRUPO_PF_EMPRESARIAL) { + if (ignorados.includes(clave)) continue; + const facturas = Number(g1Facturas.find((r: any) => r.regimen === clave)?.monto || 0); + const pagos = Number(g1Pagos.find((r: any) => r.regimen === clave)?.monto || 0); + const monto = facturas + pagos; + if (monto !== 0 || facturas !== 0 || pagos !== 0) { + porRegimen.push({ regimenClave: clave, regimenDescripcion: descMap.get(clave) || clave, monto }); + } + } + + // ─── GRUPO 2: Sueldos y Salarios (605) ─── + // Nómina recibida por el contribuyente (lado RECEPTOR). + if (!ignorados.includes('605')) { + const { rows: g2 } = await pool.query(` + SELECT COALESCE(SUM(COALESCE(total_mxn,0)), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'N' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR}${extra} + AND regimen_fiscal_receptor = '605' + `, [fechaInicio, fechaFin]); + const monto = Number(g2[0]?.monto || 0); + if (monto !== 0) { + porRegimen.push({ regimenClave: '605', regimenDescripcion: descMap.get('605') || 'Sueldos y Salarios', monto }); + } + } + + // ─── GRUPO 3: Resto de regímenes (PM y otros) ─── + // Suman I (PUE+PPD) sin restar E. Las notas de crédito tipo E se contabilizan + // del lado del receptor (gastos), no como reducción del ingreso del emisor — + // criterio fiscal vigente para PMs y otros regímenes en este grupo. + const { rows: g3Facturas } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'I' AND metodo_pago IN ('PUE', 'PPD') + AND ${VIGENTE} AND ${FR}${extra} + AND regimen_fiscal_emisor = ANY($3) + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin, GRUPO_PM_OTROS]); + + for (const clave of GRUPO_PM_OTROS) { + if (ignorados.includes(clave)) continue; + const facturas = Number(g3Facturas.find((r: any) => r.regimen === clave)?.monto || 0); + if (facturas !== 0) { + porRegimen.push({ regimenClave: clave, regimenDescripcion: descMap.get(clave) || clave, monto: facturas }); + } + } + + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Calcula el monto neto de notas de crédito tipo E PUE emitidas por el + * contribuyente en el período, agrupado por régimen del emisor. + * + * Misma fórmula neta que ingresos (`total_mxn − IMP_TRAS − EXCL_MONTO`), + * excluyendo conceptos con `clave_prod_serv` en `CLAVES_EXCLUIDAS`. + * + * No participa en el cálculo de ISR ni de ingresos — surface-only para que + * el contador vea las NCs emitidas (que ya no se restan del ingreso) sin + * perder visibilidad de la información. Mirror de `calcularNcsRecibidasPorRegimen`. + */ +export async function calcularNcsEmitidasPorRegimen( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + _ignorados?: string[], + _descMap?: Map, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise<{ total: number; porRegimen: IngresoRegimen[] }> { + const ignorados = await getIgnorados(tenantId, _ignorados); + const descMap = await getDescMap(_descMap); + const FR = getFechaRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esEmisor = ctx.esEmisor; + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + const { rows } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR}${extra} + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin]); + + const porRegimen: IngresoRegimen[] = []; + for (const row of rows) { + const clave = row.regimen as string | null; + if (!clave || ignorados.includes(clave)) continue; + const monto = Number(row.monto || 0); + if (monto === 0) continue; + porRegimen.push({ regimenClave: clave, regimenDescripcion: descMap.get(clave) || clave, monto }); + } + + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Calcula el monto neto de notas de crédito tipo E PUE RECIBIDAS por el + * contribuyente en el período, agrupado por régimen del receptor. + * + * Misma fórmula neta que ingresos/deducciones (`total_mxn − IMP_TRAS − EXCL_MONTO`), + * excluyendo conceptos con `clave_prod_serv` en `CLAVES_EXCLUIDAS`. + * + * No participa en el cálculo de ISR ni de deducciones — surface-only para que + * el contador vea las E recibidas (que ya no se restan de la deducción) sin + * perder visibilidad de la información. Mirror del lado receptor de + * `calcularEgresosEmitidosPorRegimen`. + */ +export async function calcularNcsRecibidasPorRegimen( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + _ignorados?: string[], + _descMap?: Map, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise<{ total: number; porRegimen: IngresoRegimen[] }> { + const ignorados = await getIgnorados(tenantId, _ignorados); + const descMap = await getDescMap(_descMap); + const FR = getFechaRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esReceptor = ctx.esReceptor; + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + const { rows } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR}${extra} + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin]); + + const porRegimen: IngresoRegimen[] = []; + for (const row of rows) { + const clave = row.regimen as string | null; + if (!clave || ignorados.includes(clave)) continue; + const monto = Number(row.monto || 0); + if (monto === 0) continue; + porRegimen.push({ regimenClave: clave, regimenDescripcion: descMap.get(clave) || clave, monto }); + } + + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Calcula gastos NO deducibles por Art. 27 fracción III LISR — facturas + * recibidas pagadas en efectivo con monto > $2,000. Por régimen del receptor. + * + * Suma I PUE recibidas (forma_pago='01' AND total_mxn > 2000) + complementos + * P recibidos (forma_pago='01' AND monto_pago_mxn > 2000), monto neto sin + * impuestos. Misma fórmula que deducciones (que las EXCLUYE), por lo que + * deducciones + noDeducibles = gastos brutos del periodo (excluyendo NCs). + * + * Surface-only — no entra en cálculo de ISR; sirve para que el contador vea + * cuánto está "perdiendo" por pagos en efectivo. + */ +export async function calcularGastosNoDeduciblesEfectivoPorRegimen( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + _ignorados?: string[], + _descMap?: Map, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise<{ total: number; porRegimen: IngresoRegimen[] }> { + const ignorados = await getIgnorados(tenantId, _ignorados); + const descMap = await getDescMap(_descMap); + const FR = getFechaRango(conciliacion); + const FR_PAGO = getFechaPagoRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esReceptor = ctx.esReceptor; + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + const [{ rows: facturas }, { rows: pagos }] = await Promise.all([ + pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(COALESCE(total_mxn, 0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR}${extra} + AND ${NO_DEDUCIBLE_EFECTIVO_I_PUE} + AND regimen_fiscal_receptor IS NOT NULL + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin]), + pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(COALESCE(monto_pago_mxn, 0) - (${IMP_TRAS_PAGO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${FR_PAGO}${extra} + AND ${NO_DEDUCIBLE_EFECTIVO_P} + AND regimen_fiscal_receptor IS NOT NULL + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin]), + ]); + + const map = new Map(); + for (const r of facturas) { + const k = r.regimen as string; + if (ignorados.includes(k)) continue; + map.set(k, (map.get(k) || 0) + Number(r.monto || 0)); + } + for (const r of pagos) { + const k = r.regimen as string; + if (ignorados.includes(k)) continue; + map.set(k, (map.get(k) || 0) + Number(r.monto || 0)); + } + + const porRegimen: IngresoRegimen[] = []; + for (const [k, v] of map.entries()) { + if (v === 0) continue; + porRegimen.push({ regimenClave: k, regimenDescripcion: descMap.get(k) || k, monto: v }); + } + + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Calcula el IVA NO acreditable por Art. 5 LIVA fracción I + Art. 27 fracción + * III LISR — IVA neto (trasladado − retenido) de las facturas recibidas + * pagadas en efectivo > $2,000. + * + * Mirror del lado IVA de `calcularGastosNoDeduciblesEfectivoPorRegimen`. Se + * excluye del IVA Acreditable (vía filtro en `calcularIvaBalancePorRegimen` + * y `getResumenIva`) y se exhibe aparte como card "IVA No Acreditable". + * + * Surface-only — no entra en cálculo de IVA Resultado; sirve para que el + * contador vea cuánto IVA está "perdiendo" por pagos en efectivo. + */ +export async function calcularIvaNoAcreditableEfectivoPorRegimen( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + _ignorados?: string[], + _descMap?: Map, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise<{ total: number; porRegimen: IngresoRegimen[] }> { + const ignorados = await getIgnorados(tenantId, _ignorados); + const descMap = await getDescMap(_descMap); + const FR = getFechaRango(conciliacion); + const FR_PAGO = getFechaPagoRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esReceptor = ctx.esReceptor; + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + const [{ rows: facturas }, { rows: pagos }] = await Promise.all([ + pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(${IVA_NETO} - (${EXCL_IVA_NETO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR}${extra} + AND ${NO_DEDUCIBLE_EFECTIVO_I_PUE} + AND regimen_fiscal_receptor IS NOT NULL + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin]), + pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(${IVA_NETO_PAGO}), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${FR_PAGO}${extra} + AND ${NO_DEDUCIBLE_EFECTIVO_P} + AND regimen_fiscal_receptor IS NOT NULL + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin]), + ]); + + const map = new Map(); + for (const r of facturas) { + const k = r.regimen as string; + if (ignorados.includes(k)) continue; + map.set(k, (map.get(k) || 0) + Number(r.monto || 0)); + } + for (const r of pagos) { + const k = r.regimen as string; + if (ignorados.includes(k)) continue; + map.set(k, (map.get(k) || 0) + Number(r.monto || 0)); + } + + const porRegimen: IngresoRegimen[] = []; + for (const [k, v] of map.entries()) { + if (v === 0) continue; + porRegimen.push({ regimenClave: k, regimenDescripcion: descMap.get(k) || k, monto: v }); + } + + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Calcula "Gastos del Mes" desglosados por régimen fiscal del receptor. + */ +export async function calcularEgresosPorRegimen( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + _ignorados?: string[], + _descMap?: Map, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise<{ total: number; porRegimen: EgresoRegimen[] }> { + const ignorados = await getIgnorados(tenantId, _ignorados); + const descMap = await getDescMap(_descMap); + + // Read-through cache: ver nota en calcularIngresosPorRegimen. Solo cachea + // cuando los toggles están en default — escribir/leer con flags distintos + // devolvería valores stale. + const cacheRange = considerarActivos && considerarNCs + ? planCache(fechaInicio, fechaFin, conciliacion, contribuyenteId) + : null; + if (cacheRange) { + const cached = await readEgresosFromCache(pool, cacheRange, ignorados, descMap); + if (cached) return cached; + } + + const FR = getFechaRango(conciliacion); + const FR_PAGO = getFechaPagoRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esReceptor = ctx.esReceptor; + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + const porRegimen: EgresoRegimen[] = []; + + // Gastos: lado RECEPTOR (el contribuyente recibe). Suman I PUE + P (pagos) + // + I/07 PPD compensación + nómina emitida. Las notas de crédito tipo E que + // el contribuyente recibe ya NO se restan — simétrico con el cambio en + // ingresos. + // + // I/07 PPD compensación (lado RECEPTOR): cuando el contribuyente recibe una + // I/07 PPD (aplicación de anticipo) y recibe también una E en el mismo + // mes/año cuya cfdis_relacionados contiene el UUID de esa I/07 PPD, la I/07 + // PPD aporta el equivalente de la base de la E. Se preserva por + // interpretación fiscal explícita aunque la E ya no se reste — refleja que + // la porción del servicio asociada al anticipo se reconoce como gasto al + // recibir la I/07 PPD. + // + // Filtro por RFC (esReceptor) en vez de `type` — el RFC es fuente de verdad. + // Art. 27 fracción III LISR: excluimos del cálculo de deducciones las I PUE + // y los P recibidos pagados en efectivo > $2,000. Esos gastos se exhiben + // aparte en card "No Deducibles" (calcularGastosNoDeduciblesEfectivoPorRegimen). + const { rows: facturas } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(COALESCE(total_mxn, 0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR}${extra} + AND NOT ${NO_DEDUCIBLE_EFECTIVO_I_PUE} + AND regimen_fiscal_receptor = ANY($3) + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + const { rows: pagos } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${FR_PAGO}${extra} + AND NOT ${NO_DEDUCIBLE_EFECTIVO_P} + AND regimen_fiscal_receptor = ANY($3) + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // NOTA: la compensación I/07 PPD ↔ E se eliminó por decisión del cliente + // (2026-05-02). No es un cálculo oficial del SAT y confundía a contadores — + // el contador hace la conciliación manual del ciclo anticipo→I/07 PPD→E si + // aplica. Mantener esta nota para evitar reintroducirla por intuición fiscal. + + // Nómina emitida (lado EMISOR — el contribuyente como patrón paga a empleados). + // Suma `total_mxn` completo: sin restar impuestos trasladados (la nómina típicamente + // no lleva IVA), sin restar conceptos excluidos (los códigos excluidos no aplican + // a nómina), sin filtros `considerarActivos`/`considerarNCs` (no aplican al + // concepto). Siempre por `fecha_emision` (no toca toggle de Conciliación). + // Agrupa por `regimen_fiscal_emisor` — el régimen donde está catalogado el + // contribuyente al emitir, que es donde se imputan estas deducciones. + const esEmisor = ctx.esEmisor; + const { rows: nomina } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(COALESCE(total_mxn, 0)), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'N' + AND ${VIGENTE} AND ${FECHA_RANGO} + AND regimen_fiscal_emisor = ANY($3) + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + for (const clave of TODOS_REGIMENES) { + if (ignorados.includes(clave)) continue; + const montoF = Number(facturas.find((r: any) => r.regimen === clave)?.monto || 0); + const montoP = Number(pagos.find((r: any) => r.regimen === clave)?.monto || 0); + const montoN = Number(nomina.find((r: any) => r.regimen === clave)?.monto || 0); + const monto = montoF + montoP + montoN; + if (monto !== 0 || montoF !== 0 || montoP !== 0 || montoN !== 0) { + porRegimen.push({ regimenClave: clave, regimenDescripcion: descMap.get(clave) || clave, monto }); + } + } + + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Calcula "Adquisición de Mercancías" — misma lógica que egresos pero solo CFDIs con uso_cfdi = 'G01' + */ +export async function calcularAdquisicionesMercancias( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + conciliacion?: boolean, + contribuyenteId?: string | null, +): Promise<{ total: number; porRegimen: { regimenClave: string; monto: number }[] }> { + const FR = getFechaRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esReceptor = ctx.esReceptor; + + // Adquisiciones G01 = subset de gastos con uso_cfdi='G01'. Lado receptor. + // Método A (ingenuo), consistente con calcularEgresosPorRegimen. + const { rows: facturas } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(COALESCE(total_mxn, 0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND uso_cfdi = 'G01' + AND ${VIGENTE} AND ${FR} + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin]); + + const { rows: nc } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND uso_cfdi = 'G01' + AND ${VIGENTE} AND ${FR} + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin]); + + // Consolidar por régimen + const regimenMap = new Map(); + for (const r of facturas) { + const clave = r.regimen || 'sin'; + regimenMap.set(clave, (regimenMap.get(clave) || 0) + Number(r.monto)); + } + for (const r of nc) { + const clave = r.regimen || 'sin'; + regimenMap.set(clave, (regimenMap.get(clave) || 0) - Number(r.monto)); + } + + const porRegimen = Array.from(regimenMap.entries()).map(([regimenClave, monto]) => ({ regimenClave, monto })); + const total = porRegimen.reduce((s, r) => s + r.monto, 0); + + return { total, porRegimen }; +} + +// IVA neto del comprobante: trasladado - retenido +const IVA_NETO = `COALESCE(iva_traslado_mxn,0) - COALESCE(iva_retencion_mxn,0)`; +// IVA neto del pago. Refactor 2026-04-26: campos directos, sin clamp. +// Alineado con impuestos.service.ts post-refactor (ver doc 2026-04-26-iva-refactor.md). +const IVA_NETO_PAGO = `COALESCE(iva_traslado_pago_mxn, 0) - COALESCE(iva_retencion_pago_mxn, 0)`; + +/** + * Calcula "Balance IVA" desglosado por régimen fiscal del receptor. + */ +export async function calcularIvaBalancePorRegimen( + pool: Pool, + tenantId: string, + fechaInicio: string, + fechaFin: string, + _ignorados?: string[], + _descMap?: Map, + conciliacion?: boolean, + contribuyenteId?: string | null, +): Promise<{ total: number; porRegimen: IvaBalanceRegimen[] }> { + const ignorados = await getIgnorados(tenantId, _ignorados); + const descMap = await getDescMap(_descMap); + + // Read-through cache: años pasados con contribuyente seleccionado leen de + // metricas_mensuales (iva_trasladado_total, iva_acreditable). El año actual + // y rangos parciales siguen on-the-fly. + const cacheRange = planCache(fechaInicio, fechaFin, conciliacion, contribuyenteId); + if (cacheRange) { + const cached = await readIvaBalanceFromCache(pool, cacheRange, ignorados, descMap); + if (cached) return cached; + } + + const FR = getFechaRango(conciliacion); + const FR_PAGO = getFechaPagoRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esEmisor = ctx.esEmisor; + const esReceptor = ctx.esReceptor; + + const porRegimen: IvaBalanceRegimen[] = []; + + // 6 buckets — 3 causados (emisor) + 3 acreditables (receptor). + // Filtro por RFC (esEmisor/esReceptor) en vez de type. + + // s1 — Emisor + I + PUE. + // Refactor 2026-04-26: removida la compensación I PUE/07. Las I PUE/07 + // ahora aportan IVA neto completo. La E (cualquier tipoRelación) que + // las cancele resta vía s3/r3 — fidelidad al XML, sin interpretación. + const { rows: s1 } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(${IVA_NETO} - (${EXCL_IVA_NETO})), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR} + AND regimen_fiscal_emisor = ANY($3) + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // s2 — Emisor + P + const { rows: s2 } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(${IVA_NETO_PAGO}), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${FR_PAGO} + AND regimen_fiscal_emisor = ANY($3) + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // s3 — Receptor + E + PUE (NC recibida) — resta de acreditable. + // Refactor 2026-04-26: removido filtro `<> '07'`. Todas las E PUE entran. + const { rows: s3 } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(${IVA_NETO} - (${EXCL_IVA_NETO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR} + AND regimen_fiscal_receptor = ANY($3) + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // r1 — Receptor + I + PUE. Excluye gastos en efectivo > $2k (Art. 5 LIVA + // fracción I — el IVA acreditable requiere que el gasto cumpla los requisitos + // de deducibilidad ISR; gastos en efectivo > $2k no son deducibles ni su IVA + // acreditable). + const { rows: r1 } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(${IVA_NETO} - (${EXCL_IVA_NETO})), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR} + AND NOT ${NO_DEDUCIBLE_EFECTIVO_I_PUE} + AND regimen_fiscal_receptor = ANY($3) + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // r2 — Receptor + P. Excluye P en efectivo > $2k (mismo razonamiento r1). + const { rows: r2 } = await pool.query(` + SELECT regimen_fiscal_receptor as regimen, + COALESCE(SUM(${IVA_NETO_PAGO}), 0) as monto + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${FR_PAGO} + AND NOT ${NO_DEDUCIBLE_EFECTIVO_P} + AND regimen_fiscal_receptor = ANY($3) + GROUP BY regimen_fiscal_receptor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // r3 — Emisor + E + PUE (NC emitida resta de causado). + // Refactor 2026-04-26: removido filtro `<> '07'`. Todas las E PUE entran. + const { rows: r3 } = await pool.query(` + SELECT regimen_fiscal_emisor as regimen, + COALESCE(SUM(${IVA_NETO} - (${EXCL_IVA_NETO})), 0) as monto + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${FR} + AND regimen_fiscal_emisor = ANY($3) + GROUP BY regimen_fiscal_emisor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // s4 — Emisor I PPD/07 hereda IVA neto de E que la cancelan en mismo mes. + // Mirror de SUM_E_REFERENCING en impuestos.service.ts. La I PPD/07 normalmente + // no aporta IVA (espera al P), pero si una E la referencia en su mismo mes + // hereda el IVA de la E para netear el efecto del NEG (caso PPD ↔ E). + const { rows: s4 } = await pool.query(` + SELECT i.regimen_fiscal_emisor as regimen, + COALESCE(SUM(( + SELECT COALESCE(SUM(${IVA_NETO_ALIAS('e')} - (${EXCL_IVA_NETO_ALIAS('e')})), 0) + FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND e.metodo_pago = 'PUE' + AND e.status NOT IN ('Cancelado', '0') + AND ${esEmisor.replace(/\brfc_emisor\b/g, 'e.rfc_emisor')} + AND LOWER(i.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND date_trunc('month', e.fecha_emision) = date_trunc('month', i.fecha_emision) + )), 0) as monto + FROM cfdis i + WHERE ${esEmisor.replace(/\brfc_emisor\b/g, 'i.rfc_emisor')} + AND i.tipo_comprobante = 'I' AND i.metodo_pago = 'PPD' + AND COALESCE(i.cfdi_tipo_relacion, '') = '07' + AND i.status NOT IN ('Cancelado','0') + AND ${FR.replace(/\bfecha_emision\b/g, 'i.fecha_emision')} + AND i.regimen_fiscal_emisor = ANY($3) + GROUP BY i.regimen_fiscal_emisor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + // r4 — Receptor I PPD/07 hereda IVA neto de E recibidas que la cancelan. + const { rows: r4 } = await pool.query(` + SELECT i.regimen_fiscal_receptor as regimen, + COALESCE(SUM(( + SELECT COALESCE(SUM(${IVA_NETO_ALIAS('e')} - (${EXCL_IVA_NETO_ALIAS('e')})), 0) + FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND e.metodo_pago = 'PUE' + AND e.status NOT IN ('Cancelado', '0') + AND ${esReceptor.replace(/\brfc_receptor\b/g, 'e.rfc_receptor')} + AND LOWER(i.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND date_trunc('month', e.fecha_emision) = date_trunc('month', i.fecha_emision) + )), 0) as monto + FROM cfdis i + WHERE ${esReceptor.replace(/\brfc_receptor\b/g, 'i.rfc_receptor')} + AND i.tipo_comprobante = 'I' AND i.metodo_pago = 'PPD' + AND COALESCE(i.cfdi_tipo_relacion, '') = '07' + AND i.status NOT IN ('Cancelado','0') + AND ${FR.replace(/\bfecha_emision\b/g, 'i.fecha_emision')} + AND i.regimen_fiscal_receptor = ANY($3) + GROUP BY i.regimen_fiscal_receptor + `, [fechaInicio, fechaFin, TODOS_REGIMENES]); + + const find = (rows: any[], clave: string) => Number(rows.find((r: any) => r.regimen === clave)?.monto || 0); + + // Atribución directa por lado (refactor 2026-04-26): + // Causado = (EMIT I PUE) + (EMIT P) + (EMIT I PPD/07 hereda E) − (EMIT E PUE) + // Acreditable = (RECIB I PUE) + (RECIB P) + (RECIB I PPD/07 hereda E) − (RECIB E PUE) + // Balance = Causado − Acreditable. Sin compensación I PUE/07; sin filtro + // tipoRel en E. Las I PPD/07 con E que las cancelan heredan el IVA neto de + // la E para netear dentro del mes. + for (const clave of TODOS_REGIMENES) { + if (ignorados.includes(clave)) continue; + + const causado = find(s1, clave) + find(s2, clave) + find(s4, clave) - find(r3, clave); + const acreditable = find(r1, clave) + find(r2, clave) + find(r4, clave) - find(s3, clave); + const monto = causado - acreditable; + + if (monto !== 0 || causado !== 0 || acreditable !== 0) { + porRegimen.push({ regimenClave: clave, regimenDescripcion: descMap.get(clave) || clave, monto }); + } + } + + return { total: porRegimen.reduce((s, r) => s + r.monto, 0), porRegimen }; +} + +/** + * Calcula IVA a favor acumulado mes a mes desde añoDesde/enero hasta fechaFin. + * Lógica SAT: saldo positivo se paga (no acumula), saldo negativo se arrastra. + */ +async function calcularIvaAFavorAcumulado( + pool: Pool, + tenantId: string, + fechaFin: string, + añoDesde?: number, + conciliacion?: boolean, + contribuyenteId?: string | null, +): Promise { + const añoFin = new Date(fechaFin + 'T00:00:00').getFullYear(); + const mesFin = new Date(fechaFin + 'T00:00:00').getMonth() + 1; + const inicio = añoDesde ?? añoFin; + + // Precachear + const ignorados = await getRegimenesIgnoradosClaves(tenantId); + const catalogo = await prisma.regimen.findMany({ where: { activo: true } }); + const descMap = new Map(catalogo.map(r => [r.clave, r.descripcion])); + + let saldoAFavor = 0; + + for (let y = inicio; y <= añoFin; y++) { + const ultimoMes = y === añoFin ? mesFin : 12; + for (let m = 1; m <= ultimoMes; m++) { + const lastDay = new Date(y, m, 0).getDate(); + const fi = `${y}-${String(m).padStart(2, '0')}-01`; + const ff = `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + + const ivaMes = await calcularIvaBalancePorRegimen(pool, tenantId, fi, ff, ignorados, descMap, conciliacion, contribuyenteId); + const balanceMes = ivaMes.total; + + if (balanceMes >= 0) { + if (saldoAFavor >= balanceMes) { + saldoAFavor = saldoAFavor - balanceMes; + } else { + saldoAFavor = 0; + } + } else { + saldoAFavor = saldoAFavor + Math.abs(balanceMes); + } + } + } + + return saldoAFavor; +} + +export async function getKpis( + pool: Pool, + fechaInicio: string, + fechaFin: string, + tenantId: string, + conciliacion?: boolean, + contribuyenteId?: string | null, +): Promise { + const FR = getFechaRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const esEmisor = ctx.esEmisor; + const esReceptor = ctx.esReceptor; + const ingresosData = await calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId); + const egresosData = await calcularEgresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId); + const adquisicionData = await calcularAdquisicionesMercancias(pool, tenantId, fechaInicio, fechaFin, conciliacion, contribuyenteId); + const ivaData = await calcularIvaBalancePorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId); + + // IVA a favor año actual: desde enero del año en curso + const ivaAFavorAcumulado = await calcularIvaAFavorAcumulado(pool, tenantId, fechaFin, undefined, conciliacion, contribuyenteId); + + // IVA a favor histórico: desde 5 años atrás + const añoFin = new Date(fechaFin + 'T00:00:00').getFullYear(); + const ivaAFavorHistorico = await calcularIvaAFavorAcumulado(pool, tenantId, fechaFin, añoFin - 5, conciliacion, contribuyenteId); + + // Conteos por lado: derivamos el "type" efectivo del RFC del contribuyente + // en vez de la columna `type` (que puede ser inconsistente). + const { rows: countRows } = await pool.query(` + SELECT + CASE WHEN ${esEmisor} THEN 'EMITIDO' + WHEN ${esReceptor} THEN 'RECIBIDO' + ELSE NULL END AS type, + COALESCE(CASE WHEN ${esEmisor} THEN regimen_fiscal_emisor ELSE regimen_fiscal_receptor END, '') as regimen, + COUNT(*)::int as total + FROM cfdis + WHERE ${VIGENTE} AND ${FR} + AND (${esEmisor} OR ${esReceptor}) + GROUP BY 1, regimen + `, [fechaInicio, fechaFin]); + + const ingresosVal = ingresosData.total; + const egresosVal = egresosData.total; + const utilidad = ingresosVal - egresosVal; + const margen = ingresosVal > 0 ? (utilidad / ingresosVal) * 100 : 0; + + const emitidosPorRegimen = countRows + .filter((r: any) => r.type === 'EMITIDO') + .map((r: any) => ({ regimen: r.regimen, total: r.total })); + const recibidosPorRegimen = countRows + .filter((r: any) => r.type === 'RECIBIDO') + .map((r: any) => ({ regimen: r.regimen, total: r.total })); + + return { + ingresos: ingresosVal, + ingresosPorRegimen: ingresosData.porRegimen, + egresos: egresosVal, + egresosPorRegimen: egresosData.porRegimen, + adquisicionMercancias: adquisicionData.total, + adquisicionMercanciasPorRegimen: adquisicionData.porRegimen, + utilidad, + margen: Math.round(margen * 100) / 100, + ivaBalance: ivaData.total, + ivaBalancePorRegimen: ivaData.porRegimen, + ivaAFavorAcumulado, + ivaAFavorHistorico, + cfdisEmitidos: emitidosPorRegimen.reduce((s: number, r: any) => s + r.total, 0), + cfdisEmitidosPorRegimen: emitidosPorRegimen, + cfdisRecibidos: recibidosPorRegimen.reduce((s: number, r: any) => s + r.total, 0), + cfdisRecibidosPorRegimen: recibidosPorRegimen, + }; +} + +export async function getIngresosEgresos(pool: Pool, año: number, tenantId: string, conciliacion?: boolean, contribuyenteId?: string | null): Promise { + const mesesLabel = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']; + + // Precachear catálogo e ignorados para no consultar 24 veces + const ignorados = await getRegimenesIgnoradosClaves(tenantId); + const catalogo = await prisma.regimen.findMany({ where: { activo: true } }); + const descMap = new Map(catalogo.map(r => [r.clave, r.descripcion])); + + const result: IngresosEgresosData[] = []; + + for (let m = 1; m <= 12; m++) { + const lastDay = new Date(año, m, 0).getDate(); + const fi = `${año}-${String(m).padStart(2, '0')}-01`; + const ff = `${año}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + + const ing = await calcularIngresosPorRegimen(pool, tenantId, fi, ff, ignorados, descMap, conciliacion, contribuyenteId); + const egr = await calcularEgresosPorRegimen(pool, tenantId, fi, ff, ignorados, descMap, conciliacion, contribuyenteId); + + result.push({ + mes: mesesLabel[m - 1], + ingresos: ing.total, + egresos: egr.total, + }); + } + + return result; +} + + +/** + * Devuelve los regímenes fiscales presentes en los CFDIs del rango de fechas. + */ +export async function getRegimenesDelPeriodo( + pool: Pool, + fechaInicio: string, + fechaFin: string, + conciliacion?: boolean, + contribuyenteId?: string | null, + tenantId?: string, +): Promise<{ clave: string; descripcion: string }[]> { + const FR = getFechaRango(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId || '', contribuyenteId); + const esEmisor = ctx.esEmisor; + const esReceptor = ctx.esReceptor; + // Régimen del contribuyente: emisor cuando él emitió, receptor cuando él recibió. + const { rows } = await pool.query(` + SELECT DISTINCT regimen FROM ( + SELECT regimen_fiscal_emisor AS regimen + FROM cfdis + WHERE regimen_fiscal_emisor IS NOT NULL AND ${esEmisor} AND ${FR} + UNION + SELECT regimen_fiscal_receptor AS regimen + FROM cfdis + WHERE regimen_fiscal_receptor IS NOT NULL AND ${esReceptor} AND ${FR} + ) sub + ORDER BY regimen + `, [fechaInicio, fechaFin]); + + if (rows.length === 0) return []; + + const claves = rows.map((r: any) => r.regimen); + const catalogo = await prisma.regimen.findMany({ + where: { clave: { in: claves }, activo: true }, + orderBy: { clave: 'asc' }, + }); + + return catalogo.map(r => ({ clave: r.clave, descripcion: r.descripcion })); +} + +export async function getAlertas(pool: Pool, limit = 5): Promise { + const { rows } = await pool.query(` + SELECT id, tipo, titulo, mensaje, prioridad, + fecha_vencimiento as "fechaVencimiento", + leida, resuelta, + created_at as "createdAt" + FROM alertas + WHERE resuelta = false + ORDER BY + CASE prioridad WHEN 'alta' THEN 1 WHEN 'media' THEN 2 ELSE 3 END, + created_at DESC + LIMIT $1 + `, [limit]); + + return rows; +} diff --git a/apps/api/src/services/declaraciones.service.ts b/apps/api/src/services/declaraciones.service.ts new file mode 100644 index 0000000..a0b860a --- /dev/null +++ b/apps/api/src/services/declaraciones.service.ts @@ -0,0 +1,399 @@ +import type { Pool } from 'pg'; + +// Mapeo: impuesto de la declaración → reglas para matchear obligaciones del +// contribuyente. `include` son substrings que DEBE contener el nombre de la +// obligación; `exclude` son substrings que NO debe contener. El exclude +// resuelve ambigüedades como "IVA" matcheando "Declaración de proveedores +// de IVA" (DIOT) — cuando subes pago de IVA normal, NO debe cerrar DIOT. +const IMPUESTO_A_OBLIGACION_KEYWORDS: Record = { + IVA: { include: ['iva'], exclude: ['diot', 'proveedores de iva', 'informativa'] }, + ISR: { include: ['isr'], exclude: ['retenciones', 'asimilados a salarios'] }, + IEPS: { include: ['ieps'], exclude: [] }, + SUELDOS: { include: ['sueldos', 'salarios', 'nómina'], exclude: [] }, + DIOT: { include: ['diot', 'proveedores de iva'], exclude: [] }, + OTRO: { include: [], exclude: [] }, +}; + +/** + * After uploading a declaration, find matching obligations for the contribuyente + * and mark them as completed for the period. Also resolve the ob-* alerts. + * + * Guarda `declaracion_id` en `obligacion_periodos` para que la UI pueda + * mostrar "Completada via Declaración #123" y permitir cross-link. Si la + * declaración se borra, el FK pasa a NULL (ON DELETE SET NULL) y el + * periodo sigue marcado completado — el usuario decidirá si re-abrirlo + * manualmente. + */ +async function completarObligacionesPorDeclaracion( + pool: Pool, + contribuyenteId: string, + impuestos: string[], + periodo: string, + /** UUID del usuario que subió la declaración (obligacion_periodos.completada_por es uuid). */ + completadaPor: string, + declaracionId: number, + /** Periodicidad de la declaración. Si no se provee, se asume 'mensual'. */ + periodicidad: string = 'mensual', +): Promise { + // Get active obligations for this contribuyente (incluye frecuencia para filtrar) + const { rows: obligaciones } = await pool.query<{ id: string; nombre: string; frecuencia: string | null }>( + `SELECT id, nombre, frecuencia FROM obligaciones_contribuyente WHERE contribuyente_id = $1 AND activa = true`, + [contribuyenteId], + ); + + let count = 0; + + for (const impuesto of impuestos) { + const rules = IMPUESTO_A_OBLIGACION_KEYWORDS[impuesto]; + if (!rules || rules.include.length === 0) continue; + + for (const ob of obligaciones) { + const nombreLower = ob.nombre.toLowerCase(); + const matches = rules.include.some(kw => nombreLower.includes(kw)) + && !rules.exclude.some(kw => nombreLower.includes(kw)); + if (!matches) continue; + + // Filtro por periodicidad/frecuencia: una declaración mensual no debe + // cerrar obligaciones anuales del mismo impuesto (ej. ISR mensual no + // cubre "Declaración anual de ISR"). Si la obligación tiene frecuencia + // explícita y no coincide con la periodicidad de la declaración, skip. + // `eventual` obligaciones no se tocan automáticamente. + const obFrec = (ob.frecuencia || '').toLowerCase(); + if (obFrec === 'eventual') continue; + if (obFrec && obFrec !== periodicidad.toLowerCase()) continue; + + // Mark obligation as completed for this period, with FK a la declaración + await pool.query(` + INSERT INTO obligacion_periodos (obligacion_id, periodo, completada, completada_at, completada_por, notas, declaracion_id) + VALUES ($1, $2, true, now(), $3, $4, $5) + ON CONFLICT (obligacion_id, periodo) + DO UPDATE SET completada = true, completada_at = now(), completada_por = $3, declaracion_id = $5 + `, [ob.id, periodo, completadaPor, `Declaración ${impuesto} subida`, declaracionId]); + + // Resolve the ob-* alert for this obligation+period + await pool.query( + `UPDATE alertas SET resuelta = true WHERE tipo = $1 AND resuelta = false`, + [`ob-${ob.id}-${periodo}`], + ); + + count++; + } + } + + return count; +} + +/** + * Declaraciones provisionales: PDF subido por el contador con la declaración + * presentada al SAT + opcionalmente comprobante de pago. Al subir, se marcan + * como resueltas las alertas correspondientes en la tabla `alertas` del tenant. + * + * El método legacy "marcar como realizado" desde /alertas sigue funcionando + * para usuarios que no quieran subir el documento. Esta automatización es + * adicional, no reemplaza. + */ + +export type Impuesto = 'IVA' | 'ISR' | 'IEPS' | 'SUELDOS' | 'DIOT' | 'OTRO'; + +export type Periodicidad = 'mensual' | 'bimestral' | 'trimestral' | 'semestral' | 'anual'; + +export interface DeclaracionRow { + id: number; + año: number; + mes: number; + tipo: 'normal' | 'complementaria'; + periodicidad: Periodicidad; + impuestos: string[]; + montoPago: number | null; + pdfFilename: string | null; + ligaPagoFilename: string | null; + pdfPagoFilename: string | null; + pagadoAt: string | null; + creadoPor: string | null; + notas: string | null; + createdAt: string; + updatedAt: string; + tieneLigaPago: boolean; + tienePagoPdf: boolean; +} + +// Mapeo Impuesto → prefijo de tipo de alerta (debe coincidir con +// EVENTO_A_ALERTA en alertas-manuales.service.ts). +const IMPUESTO_A_PREFIJO_DECL: Record = { + IVA: ['decl-iva'], + ISR: ['decl-isr'], + IEPS: ['decl-ieps'], + SUELDOS: ['decl-sueldos'], + DIOT: ['diot'], + OTRO: [], +}; +const IMPUESTO_A_PREFIJO_PAGO: Record = { + IVA: ['pago-iva'], + ISR: ['pago-isr'], + IEPS: ['pago-ieps'], + SUELDOS: [], // sueldos solo es declaración informativa, no tiene pago provisional + DIOT: [], + OTRO: [], +}; + +/** + * Marca como resueltas las alertas cuyo `tipo` empieza con cualquiera de los + * prefijos dados Y cuyo `fecha_vencimiento` cae en el mes/año dados. + * Idempotente: re-llamar no crea efectos secundarios extra. + */ +async function resolverAlertasPorPeriodo( + pool: Pool, + prefijos: string[], + año: number, + mes: number, +): Promise { + if (prefijos.length === 0) return 0; + // El tipo es `prefijo-YYYY-MM-DD`. Buscar por LIKE prefijo-año-mes-% + const mesStr = String(mes).padStart(2, '0'); + const conditions = prefijos.map((_, i) => `tipo LIKE $${i + 1}`).join(' OR '); + const params = prefijos.map(p => `${p}-${año}-${mesStr}-%`); + const { rowCount } = await pool.query( + `UPDATE alertas SET resuelta = true + WHERE (${conditions}) AND resuelta = false`, + params, + ); + return rowCount ?? 0; +} + +function rowToDeclaracion(r: any): DeclaracionRow { + return { + id: r.id, + año: r.año, + mes: r.mes, + tipo: r.tipo, + periodicidad: r.periodicidad || 'mensual', + impuestos: r.impuestos || [], + montoPago: r.monto_pago != null ? Number(r.monto_pago) : null, + pdfFilename: r.pdf_filename, + ligaPagoFilename: r.pdf_liga_pago_filename, + pdfPagoFilename: r.pdf_pago_filename, + pagadoAt: r.pagado_at?.toISOString() ?? null, + creadoPor: r.creado_por, + notas: r.notas, + createdAt: r.created_at.toISOString(), + updatedAt: r.updated_at.toISOString(), + tieneLigaPago: !!r.pdf_liga_pago_filename, + tienePagoPdf: !!r.pdf_pago_filename, + }; +} + +export async function listDeclaraciones( + pool: Pool, + fechaDesde?: string, + fechaHasta?: string, + contribuyenteId?: string | null, +): Promise { + const conditions: string[] = []; + const params: unknown[] = []; + + if (fechaDesde) { + params.push(fechaDesde); + conditions.push(`created_at >= $${params.length}::date`); + } + if (fechaHasta) { + params.push(fechaHasta); + conditions.push(`created_at < ($${params.length}::date + interval '1 day')`); + } + if (contribuyenteId) { + // Sanitize UUID (hex + hyphens only) + const safe = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + if (safe) { + params.push(safe); + conditions.push(`contribuyente_id = $${params.length}`); + } + } + + const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + const { rows } = await pool.query( + `SELECT id, año, mes, tipo, periodicidad, impuestos, monto_pago, pdf_filename, + pdf_liga_pago_filename, pdf_pago_filename, pagado_at, creado_por, notas, + created_at, updated_at + FROM declaraciones_provisionales + ${where} + ORDER BY created_at DESC, año DESC, mes DESC`, + params, + ); + return rows.map(rowToDeclaracion); +} + +export async function createDeclaracion( + pool: Pool, + data: { + año: number; + mes: number; + tipo: 'normal' | 'complementaria'; + periodicidad?: Periodicidad; + impuestos: string[]; + montoPago?: number | null; + pdfBase64: string; // PDF de la declaración (base64) + pdfFilename: string; + ligaPagoBase64?: string; // PDF de la liga de pago (opcional, base64) + ligaPagoFilename?: string; + notas?: string; + /** Email del usuario (para declaraciones_provisionales.creado_por VARCHAR). */ + creadoPor: string; + /** UUID del usuario (para obligacion_periodos.completada_por UUID). Opcional. */ + creadoPorUserId?: string; + contribuyenteId?: string; + }, +): Promise<{ declaracion: DeclaracionRow; alertasResueltas: number }> { + const buf = Buffer.from(data.pdfBase64, 'base64'); + const ligaBuf = data.ligaPagoBase64 ? Buffer.from(data.ligaPagoBase64, 'base64') : null; + const periodicidad = data.periodicidad || 'mensual'; + const montoPago = data.montoPago ?? null; + // If monto_pago is exactly 0, auto-mark as paid (no payment receipt needed) + const pagadoAt = montoPago === 0 ? new Date() : null; + + try { + const { rows } = await pool.query( + `INSERT INTO declaraciones_provisionales + (año, mes, tipo, periodicidad, impuestos, monto_pago, pdf_declaracion, pdf_filename, + pdf_liga_pago, pdf_liga_pago_filename, notas, creado_por, pagado_at, contribuyente_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) + RETURNING id, año, mes, tipo, periodicidad, impuestos, monto_pago, pdf_filename, + pdf_liga_pago_filename, pdf_pago_filename, pagado_at, creado_por, notas, + created_at, updated_at`, + [data.año, data.mes, data.tipo, periodicidad, data.impuestos, montoPago, + buf, data.pdfFilename, ligaBuf, data.ligaPagoFilename ?? null, + data.notas ?? null, data.creadoPor, pagadoAt, data.contribuyenteId ?? null], + ); + + const declaracion = rowToDeclaracion(rows[0]); + + // Auto-resolver alertas. Reglas: + // - tipo='normal': resuelve alertas de declaración (decl-*) del mes. + // El pago se resuelve por separado al subir comprobante. + // - tipo='complementaria': sustituye a la normal en términos de + // obligación de pago — al subirla se resuelven AMBAS (decl-* y + // pago-*) porque el cliente pagará usando la complementaria, + // no la normal. La alerta de declaración ya estaría resuelta + // si la normal se subió antes; el resolver es idempotente. + const prefijosDecl = data.impuestos.flatMap(i => IMPUESTO_A_PREFIJO_DECL[i] || []); + let alertasResueltas = await resolverAlertasPorPeriodo(pool, prefijosDecl, data.año, data.mes); + if (data.tipo === 'complementaria' || montoPago === 0) { + // complementaria: sustituye normal para pago → resolver ambas + // monto 0: nada que pagar → resolver alertas de pago también + const prefijosPago = data.impuestos.flatMap(i => IMPUESTO_A_PREFIJO_PAGO[i] || []); + alertasResueltas += await resolverAlertasPorPeriodo(pool, prefijosPago, data.año, data.mes); + } + + // Auto-complete obligaciones del contribuyente SOLO si la declaración + // también cubre el pago (complementaria sustituye a la normal para el + // pago; monto=0 significa "nada que pagar"). Una declaración normal con + // monto>0 solo presenta el acuse — la obligación de pago sigue abierta + // y se marca completada hasta que se suba el comprobante via + // `uploadComprobantePago`. Esto mantiene las alertas `pago-*` y `ob-*` + // visibles hasta que realmente se cierre el ciclo. + const cubrePago = data.tipo === 'complementaria' || montoPago === 0; + if (data.contribuyenteId && cubrePago) { + if (!data.creadoPorUserId) { + console.warn('[createDeclaracion] Sin creadoPorUserId — no se auto-completan obligaciones del contribuyente'); + } else { + const periodo = `${data.año}-${String(data.mes).padStart(2, '0')}`; + alertasResueltas += await completarObligacionesPorDeclaracion( + pool, data.contribuyenteId, data.impuestos, periodo, data.creadoPorUserId, declaracion.id, periodicidad, + ); + } + } + + return { declaracion, alertasResueltas }; + } catch (err: any) { + if (err?.code === '23505') { + throw new Error(`Ya existe una declaración tipo "normal" para ${data.mes}/${data.año}. Solo se permite una normal por mes; agrega una complementaria si necesitas corregirla.`); + } + throw err; + } +} + +export async function uploadComprobantePago( + pool: Pool, + id: number, + data: { + pdfBase64: string; + pdfFilename: string; + /** UUID del usuario que sube el comprobante (para obligacion_periodos.completada_por). */ + uploadedByUserId?: string; + }, +): Promise<{ declaracion: DeclaracionRow; alertasResueltas: number }> { + const buf = Buffer.from(data.pdfBase64, 'base64'); + + const { rows } = await pool.query( + `UPDATE declaraciones_provisionales + SET pdf_pago = $1, pdf_pago_filename = $2, pagado_at = NOW(), updated_at = NOW() + WHERE id = $3 + RETURNING id, año, mes, tipo, periodicidad, impuestos, pdf_filename, pdf_liga_pago_filename, + pdf_pago_filename, pagado_at, creado_por, notas, created_at, updated_at, + contribuyente_id`, + [buf, data.pdfFilename, id], + ); + + if (rows.length === 0) throw new Error('Declaración no encontrada'); + const row = rows[0]; + const declaracion = rowToDeclaracion(row); + + // Auto-resolver alertas de pago para los impuestos del periodo + const prefijosPago = declaracion.impuestos.flatMap(i => IMPUESTO_A_PREFIJO_PAGO[i] || []); + let alertasResueltas = await resolverAlertasPorPeriodo(pool, prefijosPago, declaracion.año, declaracion.mes); + + // Al subirse el comprobante de pago, la obligación ahora SÍ está completada + // (declaración + pago). Marcar `obligacion_periodos.completada=true` y + // resolver los `ob-*` alerts. Requires contribuyenteId (guardado en la + // declaración) y userId (del caller). + if (row.contribuyente_id && data.uploadedByUserId) { + const periodo = `${declaracion.año}-${String(declaracion.mes).padStart(2, '0')}`; + const periodicidad = row.periodicidad || 'mensual'; + alertasResueltas += await completarObligacionesPorDeclaracion( + pool, row.contribuyente_id, declaracion.impuestos, periodo, data.uploadedByUserId, declaracion.id, periodicidad, + ); + } + + return { declaracion, alertasResueltas }; +} + +export async function deleteDeclaracion(pool: Pool, id: number): Promise { + const { rowCount } = await pool.query( + `DELETE FROM declaraciones_provisionales WHERE id = $1`, + [id], + ); + if (rowCount === 0) throw new Error('Declaración no encontrada'); +} + +/** + * Cleanup: borra declaraciones con created_at < hoy - 5 años. Cumple con + * el plazo de retención del Art. 30 del CFF (contabilidad por 5 años). + * Llamado por cron diario. Idempotente: si no hay viejas, no-op. + * + * Se ejecuta por-tenant (caller pasa el pool). Returns { deleted } para log. + */ +export async function purgeDeclaracionesAntiguas(pool: Pool): Promise<{ deleted: number }> { + const { rowCount } = await pool.query( + `DELETE FROM declaraciones_provisionales + WHERE created_at < NOW() - INTERVAL '5 years'`, + ); + return { deleted: rowCount ?? 0 }; +} + +export async function getDeclaracionPdf( + pool: Pool, + id: number, + variant: 'declaracion' | 'liga' | 'pago', +): Promise<{ buffer: Buffer; filename: string } | null> { + const col = + variant === 'declaracion' ? 'pdf_declaracion' : + variant === 'liga' ? 'pdf_liga_pago' : 'pdf_pago'; + const colName = + variant === 'declaracion' ? 'pdf_filename' : + variant === 'liga' ? 'pdf_liga_pago_filename' : 'pdf_pago_filename'; + const { rows } = await pool.query( + `SELECT ${col} as data, ${colName} as filename FROM declaraciones_provisionales WHERE id = $1`, + [id], + ); + if (rows.length === 0 || !rows[0].data) return null; + return { buffer: Buffer.from(rows[0].data), filename: rows[0].filename || `declaracion-${id}.pdf` }; +} diff --git a/apps/api/src/services/despacho-stats.service.ts b/apps/api/src/services/despacho-stats.service.ts new file mode 100644 index 0000000..8fbd5c7 --- /dev/null +++ b/apps/api/src/services/despacho-stats.service.ts @@ -0,0 +1,487 @@ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; + +export interface ContribuyentesStats { + totalContribuyentes: number; + ultimaExtraccion: Date | null; + /** % global del despacho de obligaciones+tareas del periodo seleccionado completadas vs total. */ + progresoDelMes: number; + /** Declaraciones cuyo `created_at` cae en el periodo seleccionado. */ + declaracionesPresentadas: number; + /** Subset del anterior con `pdf_pago` no nulo. */ + declaracionesPagadas: number; + /** Obligaciones de declaración pendientes de periodos anteriores al seleccionado. */ + declaracionesAtrasadas: number; + /** Tareas pendientes con fecha_limite anterior al inicio del periodo seleccionado. */ + tareasAtrasadas: number; +} + +/** + * Métricas para la pestaña "Contribuyentes" del módulo Despacho (owner-only). + * + * Periodo: si se pasa `año`/`mes`, las métricas mensuales se calculan para + * ese periodo. Default = mes en curso. + * + * - totalContribuyentes / ultimaExtraccion: independientes del periodo. + * - progresoDelMes: % global obligaciones+tareas del periodo completadas. + * - declaracionesPresentadas/pagadas: declaraciones con `created_at` en el + * periodo seleccionado (presentación, no devengo). + * - declaracionesAtrasadas: obligaciones-periodo NO completadas con periodo + * anterior al seleccionado, donde la obligación sea de tipo declaración + * (categoría contiene 'mensual'/'anual'/'declaración' — heurística laxa + * ya que el catálogo no tiene un flag explícito). + * - tareasAtrasadas: tareas-periodo NO completadas con fecha_limite anterior + * al primer día del periodo seleccionado. + */ +export async function getContribuyentesStats( + pool: Pool, + tenantId: string, + año?: number, + mes?: number, +): Promise { + const { rows: [{ count }] } = await pool.query<{ count: number }>( + `SELECT COUNT(*)::int AS count + FROM contribuyentes c + JOIN entidades_gestionadas e ON e.id = c.entidad_id + WHERE e.active = true`, + ); + + const last = await prisma.satSyncJob.findFirst({ + where: { tenantId, status: 'completed' }, + orderBy: { completedAt: 'desc' }, + select: { completedAt: true }, + }); + + // Periodo: usa el filtrado o cae al mes en curso. + const now = new Date(); + const _año = año ?? now.getFullYear(); + const _mes = mes ?? now.getMonth() + 1; + const periodoMes = `${_año}-${String(_mes).padStart(2, '0')}`; + const inicioMes = `${_año}-${String(_mes).padStart(2, '0')}-01`; + const finMes = new Date(_año, _mes, 0).toISOString().split('T')[0]; + + const { rows: [progresoRow] } = await pool.query<{ total: number; completadas: number }>( + `SELECT + (SELECT COUNT(*)::int FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.activa = true AND op.periodo = $1) + + + (SELECT COUNT(*)::int FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.active = true AND tp.fecha_limite BETWEEN $2::date AND $3::date) + AS total, + (SELECT COUNT(*)::int FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.activa = true AND op.periodo = $1 AND op.completada = true) + + + (SELECT COUNT(*)::int FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.active = true AND tp.fecha_limite BETWEEN $2::date AND $3::date AND tp.completada = true) + AS completadas`, + [periodoMes, inicioMes, finMes], + ); + const progresoDelMes = progresoRow.total > 0 + ? Math.round((progresoRow.completadas / progresoRow.total) * 100) + : 0; + + const { rows: [decRow] } = await pool.query<{ presentadas: number; pagadas: number }>( + `SELECT + COUNT(*)::int AS presentadas, + COUNT(*) FILTER (WHERE pdf_pago IS NOT NULL)::int AS pagadas + FROM declaraciones_provisionales + WHERE created_at >= $1::date AND created_at < ($2::date + interval '1 day')`, + [inicioMes, finMes], + ); + + // Atrasadas de periodos anteriores al seleccionado. + // Para declaraciones (obligaciones) usamos `op.periodo < periodoMes`. + // Heurística "es declaración": categoría contiene 'mensual', 'anual', + // 'declaración' o el nombre incluye 'declaración' (case insensitive). + const { rows: [atrRow] } = await pool.query<{ decl_atr: number; tar_atr: number }>( + `SELECT + (SELECT COUNT(*)::int FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.activa = true + AND op.completada = false + AND op.periodo < $1 + AND ( + LOWER(COALESCE(oc.categoria, '')) ~ 'mensual|anual|declarac' + OR LOWER(oc.nombre) LIKE '%declarac%' + ) + ) AS decl_atr, + (SELECT COUNT(*)::int FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.active = true + AND tp.completada = false + AND tp.fecha_limite < $2::date + ) AS tar_atr`, + [periodoMes, inicioMes], + ); + + return { + totalContribuyentes: count, + ultimaExtraccion: last?.completedAt ?? null, + progresoDelMes, + declaracionesPresentadas: decRow.presentadas, + declaracionesPagadas: decRow.pagadas, + declaracionesAtrasadas: atrRow.decl_atr, + tareasAtrasadas: atrRow.tar_atr, + }; +} + +export interface ContribuyenteAsignado { + contribuyenteId: string; + rfc: string; + nombre: string; + carteraNombre: string | null; + obligacionesPendientes: number; + obligacionesAtrasadas: number; + obligacionesCompletadas: number; + tareasPendientes: number; + tareasAtrasadas: number; + tareasCompletadas: number; +} + +/** + * Resuelve los contribuyentes asignados al usuario actual según su rol y la + * estructura de carteras: + * + * - **owner / cfo**: TODOS los contribuyentes del despacho. + * - **supervisor**: contribuyentes que están en una cartera donde + * `c.supervisor_user_id = userId` o en una subcartera de tales carteras. + * - **auxiliar**: contribuyentes en carteras donde `c.auxiliar_user_id = userId`. + * - **otros (contador, cliente, etc.)**: vacío. + * + * Las métricas se calculan usando el periodo `año`/`mes` como pivote: lo + * "atrasado" es lo NO completado de periodos anteriores al filtrado. + */ +export async function getMisAsignados( + pool: Pool, + userId: string, + userRole: string, + año?: number, + mes?: number, +): Promise { + let baseFilter: string; + const params: unknown[] = []; + if (userRole === 'owner' || userRole === 'cfo') { + baseFilter = `e.active = true`; + } else if (userRole === 'supervisor') { + params.push(userId); + baseFilter = `e.active = true AND ce.cartera_id IN ( + SELECT id FROM carteras WHERE supervisor_user_id = $1 + UNION + SELECT id FROM carteras WHERE parent_id IN ( + SELECT id FROM carteras WHERE supervisor_user_id = $1 + ) + )`; + } else if (userRole === 'auxiliar') { + params.push(userId); + baseFilter = `e.active = true AND ce.cartera_id IN ( + SELECT id FROM carteras WHERE auxiliar_user_id = $1 + )`; + } else { + return []; + } + + const { rows } = await pool.query( + `SELECT DISTINCT c.entidad_id AS contribuyente_id, c.rfc, e.nombre, + (SELECT cart.nombre FROM carteras cart + JOIN cartera_entidades cee ON cee.cartera_id = cart.id + WHERE cee.entidad_id = c.entidad_id LIMIT 1) AS cartera_nombre + FROM contribuyentes c + JOIN entidades_gestionadas e ON e.id = c.entidad_id + LEFT JOIN cartera_entidades ce ON ce.entidad_id = c.entidad_id + WHERE ${baseFilter} + ORDER BY e.nombre`, + params, + ); + + // Para cada contribuyente, contar pendientes/atrasados/completados de obligaciones+tareas. + // Hacemos una sola query agregada por contribuyente con CTEs. + const ids = rows.map(r => r.contribuyente_id); + if (ids.length === 0) return []; + + // Periodo pivote: usa el filtrado o cae al mes en curso. + const now = new Date(); + const _año = año ?? now.getFullYear(); + const _mes = mes ?? now.getMonth() + 1; + const periodoMes = `${_año}-${String(_mes).padStart(2, '0')}`; + const inicioMes = `${_año}-${String(_mes).padStart(2, '0')}-01`; + const finMes = new Date(_año, _mes, 0).toISOString().split('T')[0]; + + const { rows: stats } = await pool.query( + `WITH obl AS ( + SELECT oc.contribuyente_id, + COUNT(*) FILTER (WHERE op.completada = false AND op.periodo = $1)::int AS pendientes, + COUNT(*) FILTER (WHERE op.completada = false AND op.periodo < $1)::int AS atrasadas, + COUNT(*) FILTER (WHERE op.completada = true AND op.periodo = $1)::int AS completadas + FROM obligaciones_contribuyente oc + LEFT JOIN obligacion_periodos op ON op.obligacion_id = oc.id + WHERE oc.contribuyente_id = ANY($4::uuid[]) AND oc.activa = true + GROUP BY oc.contribuyente_id + ), + tar AS ( + SELECT tc.contribuyente_id, + COUNT(*) FILTER (WHERE tp.completada = false AND tp.fecha_limite BETWEEN $2::date AND $3::date)::int AS pendientes, + COUNT(*) FILTER (WHERE tp.completada = false AND tp.fecha_limite < $2::date)::int AS atrasadas, + COUNT(*) FILTER (WHERE tp.completada = true AND tp.fecha_limite BETWEEN $2::date AND $3::date)::int AS completadas + FROM tareas_catalogo tc + LEFT JOIN tarea_periodos tp ON tp.tarea_id = tc.id + WHERE tc.contribuyente_id = ANY($4::uuid[]) AND tc.active = true + GROUP BY tc.contribuyente_id + ) + SELECT + obl.contribuyente_id AS obl_id, obl.pendientes AS obl_pen, obl.atrasadas AS obl_atr, obl.completadas AS obl_com, + tar.contribuyente_id AS tar_id, tar.pendientes AS tar_pen, tar.atrasadas AS tar_atr, tar.completadas AS tar_com + FROM obl + FULL OUTER JOIN tar ON tar.contribuyente_id = obl.contribuyente_id`, + [periodoMes, inicioMes, finMes, ids], + ); + + const statsMap = new Map(); + for (const s of stats) { + const id = s.obl_id || s.tar_id; + if (!id) continue; + statsMap.set(id, { + obl: { pen: s.obl_pen ?? 0, atr: s.obl_atr ?? 0, com: s.obl_com ?? 0 }, + tar: { pen: s.tar_pen ?? 0, atr: s.tar_atr ?? 0, com: s.tar_com ?? 0 }, + }); + } + + const result = rows.map(r => { + const s = statsMap.get(r.contribuyente_id); + return { + contribuyenteId: r.contribuyente_id, + rfc: r.rfc, + nombre: r.nombre, + carteraNombre: r.cartera_nombre, + obligacionesPendientes: s?.obl.pen ?? 0, + obligacionesAtrasadas: s?.obl.atr ?? 0, + obligacionesCompletadas: s?.obl.com ?? 0, + tareasPendientes: s?.tar.pen ?? 0, + tareasAtrasadas: s?.tar.atr ?? 0, + tareasCompletadas: s?.tar.com ?? 0, + }; + }); + // Ordena por atrasos descendente — los más rezagados arriba. + result.sort((a, b) => { + const atrasoA = a.obligacionesAtrasadas + a.tareasAtrasadas; + const atrasoB = b.obligacionesAtrasadas + b.tareasAtrasadas; + if (atrasoA !== atrasoB) return atrasoB - atrasoA; + return a.nombre.localeCompare(b.nombre); + }); + return result; +} + +export interface MiembroEquipo { + userId: string; + nombre: string; + email: string; + rol: 'supervisor' | 'auxiliar'; + contribuyentes: number; + obligacionesAtrasadas: number; + tareasAtrasadas: number; + totalPendientes: number; + /** completadas + pendientes del periodo filtrado (sin atrasos). */ + totalPeriodo: number; + completadasPeriodo: number; + avancePct: number | null; +} + +export interface SupervisorConAuxiliares extends MiembroEquipo { + auxiliares: MiembroEquipo[]; +} + +export interface EquipoStatsResponse { + supervisores: SupervisorConAuxiliares[]; + /** Auxiliares activos sin entrada en `auxiliar_supervisores`. Solo owner los ve. */ + huerfanos: MiembroEquipo[]; +} + +/** + * Resumen de avance por miembro del equipo, en estructura jerárquica + * supervisor → auxiliares + lista de auxiliares "huérfanos" (sin supervisor). + * + * - **owner / cfo**: ve TODOS los supervisores + auxiliares sin supervisor. + * - **supervisor**: ve solo a sí mismo con sus auxiliares (sin huérfanos). + * + * Métricas calculadas por periodo (`año`/`mes` o mes en curso). + */ +export async function getEquipoStats( + pool: Pool, + userId: string, + userRole: string, + tenantId: string, + año?: number, + mes?: number, +): Promise { + // 1. Construir mapa supervisor → auxiliares. + // + // La relación se infiere desde `carteras`: + // - Si una cartera tiene `auxiliar_user_id` Y `supervisor_user_id` no nulos, + // ese par directo cuenta. + // - Si una subcartera (parent_id no nulo) tiene `auxiliar_user_id`, su + // supervisor es el `supervisor_user_id` del parent. + // + // Fallback: tabla legacy `auxiliar_supervisores`. La unión con DISTINCT + // evita duplicados si un auxiliar aparece en ambas fuentes. + const { rows: paresRows } = await pool.query<{ supervisor_user_id: string; auxiliar_user_id: string }>( + `SELECT DISTINCT supervisor_user_id, auxiliar_user_id FROM ( + SELECT c.supervisor_user_id, c.auxiliar_user_id + FROM carteras c + WHERE c.auxiliar_user_id IS NOT NULL + AND c.supervisor_user_id IS NOT NULL + UNION + SELECT p.supervisor_user_id, sub.auxiliar_user_id + FROM carteras sub + JOIN carteras p ON p.id = sub.parent_id + WHERE sub.auxiliar_user_id IS NOT NULL + AND p.supervisor_user_id IS NOT NULL + UNION + SELECT supervisor_user_id, auxiliar_user_id FROM auxiliar_supervisores + ) t WHERE supervisor_user_id IS NOT NULL AND auxiliar_user_id IS NOT NULL`, + ); + + let pares = paresRows.map(r => ({ supervisorId: r.supervisor_user_id, auxiliarId: r.auxiliar_user_id })); + if (userRole === 'supervisor') { + pares = pares.filter(p => p.supervisorId === userId); + } else if (userRole !== 'owner' && userRole !== 'cfo') { + return { supervisores: [], huerfanos: [] }; + } + + // 2. Agrupar auxiliares por supervisor. + const supervisorIds = [...new Set(pares.map(p => p.supervisorId))]; + const auxiliaresPorSup = new Map(); + for (const p of pares) { + if (!auxiliaresPorSup.has(p.supervisorId)) auxiliaresPorSup.set(p.supervisorId, []); + auxiliaresPorSup.get(p.supervisorId)!.push(p.auxiliarId); + } + + // 3. Para cada user (supervisor o auxiliar), calcular su miembro. + const result: SupervisorConAuxiliares[] = []; + for (const supId of supervisorIds) { + const supMiembro = await calcularMiembro(pool, supId, 'supervisor', año, mes); + if (!supMiembro) continue; + const auxiliares: MiembroEquipo[] = []; + for (const auxId of auxiliaresPorSup.get(supId) ?? []) { + const auxMiembro = await calcularMiembro(pool, auxId, 'auxiliar', año, mes); + if (auxMiembro) auxiliares.push(auxMiembro); + } + auxiliares.sort((a, b) => b.totalPendientes - a.totalPendientes); + result.push({ ...supMiembro, auxiliares }); + } + + result.sort((a, b) => b.totalPendientes - a.totalPendientes); + + // 4. Auxiliares "huérfanos" (sin entrada en auxiliar_supervisores). Solo + // el owner los ve para que pueda asignarles supervisor. + let huerfanos: MiembroEquipo[] = []; + if (userRole === 'owner' || userRole === 'cfo') { + const auxiliaresMapeados = new Set(pares.map(p => p.auxiliarId)); + const auxiliaresActivos = await prisma.tenantMembership.findMany({ + where: { tenantId, active: true, rol: { nombre: 'auxiliar' } }, + include: { user: { select: { id: true, active: true } } }, + }); + const huerfanosIds = auxiliaresActivos + .filter(m => m.user.active && !auxiliaresMapeados.has(m.userId)) + .map(m => m.userId); + for (const auxId of huerfanosIds) { + const aux = await calcularMiembro(pool, auxId, 'auxiliar', año, mes); + if (aux) huerfanos.push(aux); + } + huerfanos.sort((a, b) => b.totalPendientes - a.totalPendientes); + } + + return { supervisores: result, huerfanos }; +} + +async function calcularMiembro( + pool: Pool, + uId: string, + rol: 'supervisor' | 'auxiliar', + año?: number, + mes?: number, +): Promise { + const userInfo = await prisma.user.findUnique({ + where: { id: uId }, + select: { nombre: true, email: true, active: true }, + }); + if (!userInfo || !userInfo.active) return null; + + const filter = rol === 'supervisor' + ? `ce.cartera_id IN (SELECT id FROM carteras WHERE supervisor_user_id = $1 + UNION SELECT id FROM carteras WHERE parent_id IN (SELECT id FROM carteras WHERE supervisor_user_id = $1))` + : `ce.cartera_id IN (SELECT id FROM carteras WHERE auxiliar_user_id = $1)`; + + const { rows: contribRows } = await pool.query<{ entidad_id: string }>( + `SELECT DISTINCT ce.entidad_id FROM cartera_entidades ce WHERE ${filter}`, + [uId], + ); + const contribIds = contribRows.map(r => r.entidad_id); + + // Periodo pivote + const now = new Date(); + const _año = año ?? now.getFullYear(); + const _mes = mes ?? now.getMonth() + 1; + const periodoMes = `${_año}-${String(_mes).padStart(2, '0')}`; + const inicioMes = `${_año}-${String(_mes).padStart(2, '0')}-01`; + const finMes = new Date(_año, _mes, 0).toISOString().split('T')[0]; + + let obl = 0, tar = 0, total = 0, completadas = 0; + if (contribIds.length > 0) { + const { rows: [agg] } = await pool.query<{ + obl_atr: number; tar_atr: number; + obl_pen: number; obl_com: number; + tar_pen: number; tar_com: number; + }>( + `SELECT + (SELECT COUNT(*)::int FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.contribuyente_id = ANY($1::uuid[]) + AND oc.activa = true AND op.completada = false AND op.periodo < $2) AS obl_atr, + (SELECT COUNT(*)::int FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.contribuyente_id = ANY($1::uuid[]) + AND tc.active = true AND tp.completada = false AND tp.fecha_limite < $3::date) AS tar_atr, + (SELECT COUNT(*)::int FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.contribuyente_id = ANY($1::uuid[]) + AND oc.activa = true AND op.periodo = $2 AND op.completada = false) AS obl_pen, + (SELECT COUNT(*)::int FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + WHERE oc.contribuyente_id = ANY($1::uuid[]) + AND oc.activa = true AND op.periodo = $2 AND op.completada = true) AS obl_com, + (SELECT COUNT(*)::int FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.contribuyente_id = ANY($1::uuid[]) + AND tc.active = true AND tp.fecha_limite BETWEEN $3::date AND $4::date AND tp.completada = false) AS tar_pen, + (SELECT COUNT(*)::int FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.contribuyente_id = ANY($1::uuid[]) + AND tc.active = true AND tp.fecha_limite BETWEEN $3::date AND $4::date AND tp.completada = true) AS tar_com`, + [contribIds, periodoMes, inicioMes, finMes], + ); + obl = agg.obl_atr; + tar = agg.tar_atr; + completadas = agg.obl_com + agg.tar_com; + total = completadas + agg.obl_pen + agg.tar_pen; + } + + return { + userId: uId, + nombre: userInfo.nombre, + email: userInfo.email, + rol, + contribuyentes: contribIds.length, + obligacionesAtrasadas: obl, + tareasAtrasadas: tar, + totalPendientes: obl + tar, + totalPeriodo: total, + completadasPeriodo: completadas, + avancePct: total > 0 ? Math.round((completadas / total) * 100) : null, + }; +} diff --git a/apps/api/src/services/despacho.service.ts b/apps/api/src/services/despacho.service.ts new file mode 100644 index 0000000..8a59add --- /dev/null +++ b/apps/api/src/services/despacho.service.ts @@ -0,0 +1,147 @@ +import { prisma, tenantDb } from '../config/database.js'; +import { hashPassword } from '../auth/passwords.js'; +import { generateAccessToken, generateRefreshToken } from '../auth/tokens.js'; +import type { DespachoSignupRequest } from '@horux/shared'; +import type { JWTPayload, Role } from '@horux/shared'; +import { emailService } from './email/email.service.js'; + +export async function signupDespacho(data: DespachoSignupRequest) { + const { despacho, owner } = data; + + const existingUser = await prisma.user.findUnique({ where: { email: owner.email } }); + if (existingUser) { + throw new Error('Ya existe un usuario con este email'); + } + + const passwordHash = await hashPassword(owner.password); + + const tenantSlug = `despacho_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`; + const databaseName = `horux_${tenantSlug}`; + + const result = await prisma.$transaction(async (tx) => { + const tenant = await tx.tenant.create({ + data: { + nombre: despacho.nombre, + rfc: tenantSlug.toUpperCase(), + plan: 'trial', + databaseName: databaseName, + verticalProfile: despacho.verticalProfile as any, + dbMode: (despacho.plan === 'business_control' ? 'BYO' : 'MANAGED') as any, + dbSchemaVersion: 0, + trialEndsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), + codigoPostal: despacho.codigoPostal, + }, + }); + + const user = await tx.user.create({ + data: { + email: owner.email.toLowerCase(), + passwordHash, + nombre: owner.nombre, + lastTenantId: tenant.id, + }, + }); + + const ownerRole = await tx.rol.findUnique({ where: { nombre: 'owner' } }); + if (!ownerRole) throw new Error('Rol owner no encontrado en BD'); + + await tx.tenantMembership.create({ + data: { + userId: user.id, + tenantId: tenant.id, + rolId: ownerRole.id, + isOwner: true, + }, + }); + + return { tenant, user }; + }); + + try { + await tenantDb.provisionDatabase(tenantSlug, databaseName); + } catch (err: any) { + await prisma.tenant.delete({ where: { id: result.tenant.id } }).catch(() => {}); + await prisma.user.delete({ where: { id: result.user.id } }).catch(() => {}); + throw new Error(`Error al crear base de datos del despacho: ${err.message}`); + } + + const payload: Omit = { + userId: result.user.id, + email: result.user.email, + role: 'owner' as Role, + tenantId: result.tenant.id, + tokenVersion: 0, + }; + + const accessToken = generateAccessToken(payload); + const refreshToken = generateRefreshToken(payload); + + await prisma.refreshToken.create({ + data: { + userId: result.user.id, + token: refreshToken, + expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + }, + }); + + // Send welcome email (fire-and-forget) + emailService.sendDespachoWelcome(owner.email, { + nombre: result.user.nombre, + despachoNombre: result.tenant.nombre, + email: result.user.email, + }).catch(err => console.error('[Signup] Welcome email failed:', err)); + + // If paid plan, create MP checkout via subscriptionService.subscribe() + // que también crea la fila Subscription en BD (clave para que el webhook + // pueda aplicar la dualidad firstYear→renewal tras el primer cobro aprobado). + let paymentUrl: string | undefined; + if (data.despacho.plan && data.despacho.plan !== 'trial') { + try { + const subscriptionService = await import('./payment/subscription.service.js'); + const result2 = await subscriptionService.subscribe({ + tenantId: result.tenant.id, + plan: data.despacho.plan as any, + // mi_empresa(+) acepta monthly/annual; los demás solo annual + // — el subscribe valida y rechaza monthly cuando no aplica. + frequency: data.despacho.frequency || 'annual', + payerEmail: owner.email, + }); + paymentUrl = result2.paymentUrl; + } catch (err: any) { + // Rollback: delete tenant + user since payment couldn't be set up + await prisma.tenantMembership.deleteMany({ where: { tenantId: result.tenant.id } }).catch(() => {}); + await prisma.refreshToken.deleteMany({ where: { userId: result.user.id } }).catch(() => {}); + await prisma.tenant.delete({ where: { id: result.tenant.id } }).catch(() => {}); + await prisma.user.delete({ where: { id: result.user.id } }).catch(() => {}); + const msg = err?.message || ''; + if (msg.includes('MercadoPago no está configurado') || msg.includes('Unauthorized access')) { + throw new Error('No se pudo procesar el cobro. Verifica que el sistema de pagos esté configurado o selecciona el plan Trial.'); + } + throw new Error(msg || 'No se pudo procesar el cobro.'); + } + } + + return { + accessToken, + refreshToken, + paymentUrl, + user: { + id: result.user.id, + email: result.user.email, + nombre: result.user.nombre, + role: 'owner' as Role, + tenantId: result.tenant.id, + tenantName: result.tenant.nombre, + tenantRfc: result.tenant.rfc, + plan: result.tenant.plan, + tenants: [{ + id: result.tenant.id, + nombre: result.tenant.nombre, + rfc: result.tenant.rfc, + plan: result.tenant.plan, + role: 'owner' as Role, + isOwner: true, + }], + }, + }; +} diff --git a/apps/api/src/services/documentos-extras.service.ts b/apps/api/src/services/documentos-extras.service.ts new file mode 100644 index 0000000..332392a --- /dev/null +++ b/apps/api/src/services/documentos-extras.service.ts @@ -0,0 +1,129 @@ +import type { Pool } from 'pg'; + +export interface DocumentoExtra { + id: number; + contribuyenteId: string | null; + nombre: string; + descripcion: string | null; + categoria: string | null; + pdfFilename: string; + subidoPor: string | null; + createdAt: string; +} + +interface CreateExtraInput { + contribuyenteId?: string | null; + nombre: string; + descripcion?: string | null; + categoria?: string | null; + pdfBase64: string; + pdfFilename: string; + subidoPor: string; +} + +function sanitizeUuid(id: string): string { + return id.replace(/[^a-f0-9-]/gi, ''); +} + +export async function createExtra( + pool: Pool, + data: CreateExtraInput, +): Promise { + const pdfBuffer = Buffer.from(data.pdfBase64, 'base64'); + const contribuyenteId = data.contribuyenteId + ? sanitizeUuid(data.contribuyenteId) + : null; + const { rows } = await pool.query( + `INSERT INTO documentos_extras + (contribuyente_id, nombre, descripcion, categoria, pdf, pdf_filename, subido_por) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, contribuyente_id AS "contribuyenteId", nombre, descripcion, + categoria, pdf_filename AS "pdfFilename", subido_por AS "subidoPor", + created_at AS "createdAt"`, + [ + contribuyenteId, + data.nombre, + data.descripcion ?? null, + data.categoria ?? null, + pdfBuffer, + data.pdfFilename, + data.subidoPor, + ], + ); + const r = rows[0]; + return { ...r, createdAt: r.createdAt?.toISOString?.() ?? r.createdAt }; +} + +export async function listExtras( + pool: Pool, + contribuyenteId?: string | null, + categoria?: string | null, +): Promise { + const params: any[] = []; + const where: string[] = []; + if (contribuyenteId) { + params.push(sanitizeUuid(contribuyenteId)); + where.push(`contribuyente_id = $${params.length}`); + } + if (categoria) { + params.push(categoria); + where.push(`categoria = $${params.length}`); + } + const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : ''; + const { rows } = await pool.query( + `SELECT id, contribuyente_id AS "contribuyenteId", nombre, descripcion, + categoria, pdf_filename AS "pdfFilename", subido_por AS "subidoPor", + created_at AS "createdAt" + FROM documentos_extras + ${whereClause} + ORDER BY created_at DESC + LIMIT 500`, + params, + ); + return rows.map((r: any) => ({ + ...r, + createdAt: r.createdAt?.toISOString?.() ?? r.createdAt, + })); +} + +export async function getExtraPdf( + pool: Pool, + id: number, +): Promise<{ buffer: Buffer; filename: string; nombre: string } | null> { + const { rows } = await pool.query( + `SELECT pdf, pdf_filename AS "pdfFilename", nombre + FROM documentos_extras WHERE id = $1`, + [id], + ); + if (rows.length === 0) return null; + return { + buffer: rows[0].pdf, + filename: rows[0].pdfFilename, + nombre: rows[0].nombre, + }; +} + +export async function deleteExtra(pool: Pool, id: number): Promise { + const { rowCount } = await pool.query( + `DELETE FROM documentos_extras WHERE id = $1`, + [id], + ); + return (rowCount ?? 0) > 0; +} + +export async function listCategorias( + pool: Pool, + contribuyenteId?: string | null, +): Promise { + const params: any[] = []; + let where = `WHERE categoria IS NOT NULL AND categoria != ''`; + if (contribuyenteId) { + params.push(sanitizeUuid(contribuyenteId)); + where += ` AND contribuyente_id = $${params.length}`; + } + const { rows } = await pool.query( + `SELECT DISTINCT categoria FROM documentos_extras ${where} ORDER BY categoria`, + params, + ); + return rows.map((r: any) => r.categoria); +} diff --git a/apps/api/src/services/email/email.service.ts b/apps/api/src/services/email/email.service.ts new file mode 100644 index 0000000..6ceb8ed --- /dev/null +++ b/apps/api/src/services/email/email.service.ts @@ -0,0 +1,210 @@ +import { createEmailTransport } from '@horux/core'; +import { env } from '../../config/env.js'; + +const transport = createEmailTransport( + env.SMTP_USER && env.SMTP_PASS + ? { + host: env.SMTP_HOST, + port: parseInt(env.SMTP_PORT), + user: env.SMTP_USER, + pass: env.SMTP_PASS, + from: env.SMTP_FROM, + } + : null +); + +async function sendEmail(to: string, subject: string, html: string) { + await transport.send(to, subject, html); +} + +export const emailService = { + sendWelcome: async (to: string, data: { nombre: string; email: string; tempPassword: string }) => { + const { welcomeEmail } = await import('./templates/welcome.js'); + await sendEmail(to, 'Bienvenido a Horux360', welcomeEmail(data)); + }, + + sendPasswordReset: async (to: string, data: { nombre: string; resetUrl: string }) => { + const { passwordResetEmail } = await import('./templates/password-reset.js'); + await sendEmail(to, 'Recuperación de contraseña - Horux360', passwordResetEmail(data)); + }, + + sendFielNotification: async (data: { clienteNombre: string; clienteRfc: string }) => { + const { fielNotificationEmail } = await import('./templates/fiel-notification.js'); + await sendEmail(env.ADMIN_EMAIL, `[${data.clienteNombre}] subió su FIEL`, fielNotificationEmail(data)); + }, + + sendPaymentConfirmed: async (to: string, data: { nombre: string; amount: number; plan: string; date: string }) => { + const { paymentConfirmedEmail } = await import('./templates/payment-confirmed.js'); + await sendEmail(to, 'Confirmación de pago - Horux360', paymentConfirmedEmail(data)); + }, + + sendPaymentFailed: async (to: string, data: { nombre: string; amount: number; plan: string }) => { + const { paymentFailedEmail } = await import('./templates/payment-failed.js'); + await sendEmail(to, 'Problema con tu pago - Horux360', paymentFailedEmail(data)); + await sendEmail(env.ADMIN_EMAIL, `Pago fallido: ${data.nombre}`, paymentFailedEmail(data)); + }, + + sendSubscriptionExpiring: async (to: string, data: { nombre: string; plan: string; expiresAt: string }) => { + const { subscriptionExpiringEmail } = await import('./templates/subscription-expiring.js'); + await sendEmail(to, 'Tu suscripción vence en 5 días', subscriptionExpiringEmail(data)); + }, + + sendSubscriptionCancelled: async (to: string, data: { nombre: string; plan: string }) => { + const { subscriptionCancelledEmail } = await import('./templates/subscription-cancelled.js'); + await sendEmail(to, 'Suscripción cancelada - Horux360', subscriptionCancelledEmail(data)); + await sendEmail(env.ADMIN_EMAIL, `Suscripción cancelada: ${data.nombre}`, subscriptionCancelledEmail(data)); + }, + + sendNewClientAdmin: async (data: { + clienteNombre: string; + clienteRfc: string; + adminEmail: string; + adminNombre: string; + tempPassword: string; + databaseName: string; + plan: string; + }) => { + const { newClientAdminEmail } = await import('./templates/new-client-admin.js'); + await sendEmail(env.ADMIN_EMAIL, `Nuevo cliente: ${data.clienteNombre} (${data.clienteRfc})`, newClientAdminEmail(data)); + }, + + sendWeeklyUpdate: async (to: string, data: import('./templates/weekly-update.js').WeeklyUpdateData) => { + const { weeklyUpdateEmail } = await import('./templates/weekly-update.js'); + await sendEmail(to, `Actualización semanal — ${data.empresa}`, weeklyUpdateEmail(data)); + }, + + sendDespachoWelcome: async (to: string, data: { nombre: string; despachoNombre: string; email: string }) => { + const { despachoWelcomeEmail } = await import('./templates/despacho-welcome.js'); + await sendEmail(to, `Bienvenido a Horux Despachos — ${data.despachoNombre}`, despachoWelcomeEmail(data)); + }, + + sendTrialReminder: async (to: string, data: { nombre: string; despachoNombre: string; diasRestantes: number; wizardCompleto: boolean }) => { + const { trialReminderEmail } = await import('./templates/trial-reminder.js'); + const subject = data.diasRestantes <= 3 + ? `⚠️ Tu trial termina en ${data.diasRestantes} días — ${data.despachoNombre}` + : `Quedan ${data.diasRestantes} días de trial — ${data.despachoNombre}`; + await sendEmail(to, subject, trialReminderEmail(data)); + }, + + sendTrialExpired: async (to: string, data: { nombre: string; despachoNombre: string }) => { + const { trialExpiredEmail } = await import('./templates/trial-reminder.js'); + await sendEmail(to, `Prueba finalizada — ${data.despachoNombre}`, trialExpiredEmail(data)); + }, + + /** + * Notifica la subida de una declaración o documento extra al despacho. + * `recipients` debe venir deduplicado por el caller. El subject se + * genera a partir del kind y RFC del contribuyente. + */ + sendDocumentoSubido: async ( + recipients: string[], + data: import('./templates/documento-subido.js').DocumentoSubidoData, + ) => { + if (recipients.length === 0) return; + const { documentoSubidoEmail } = await import('./templates/documento-subido.js'); + const html = documentoSubidoEmail(data); + const subject = data.kind === 'declaracion' + ? `📄 Declaración subida — ${data.contribuyenteRfc}${data.declaracion ? ` (${data.declaracion.impuestos.join('/')} ${data.declaracion.periodo})` : ''}` + : `📎 Documento subido — ${data.contribuyenteRfc}${data.extra ? `: ${data.extra.nombre}` : ''}`; + // Envío secuencial; fire-and-forget a nivel del caller. Un error en un + // destinatario NO debe impedir enviar al siguiente. + for (const to of recipients) { + try { + await sendEmail(to, subject, html); + } catch (err: any) { + console.error(`[Email] Fallo enviando documento-subido a ${to}:`, err?.message || err); + } + } + }, + + /** + * Notifica al auxiliar de la cartera que un supervisor/owner marcó como + * completada una tarea con `solo_supervisor_completa=true`. + */ + sendTareaCompletada: async ( + to: string, + data: import('./templates/tarea-completada.js').TareaCompletadaData, + ) => { + const { tareaCompletadaEmail } = await import('./templates/tarea-completada.js'); + await sendEmail( + to, + `✓ ${data.tareaNombre} — ${data.contribuyenteRfc}`, + tareaCompletadaEmail(data), + ); + }, + + /** Aprobadores reciben aviso cuando se sube papelería que requiere aprobación. */ + sendPapeleriaAprobacionRequerida: async ( + to: string, + data: import('./templates/papeleria.js').PapeleriaAprobacionRequeridaData, + ) => { + const { papeleriaAprobacionRequeridaEmail } = await import('./templates/papeleria.js'); + await sendEmail( + to, + `📋 Papelería pendiente — ${data.contribuyenteRfc} (${data.periodo})`, + papeleriaAprobacionRequeridaEmail(data), + ); + }, + + /** Uploader recibe aviso cuando aprueban o rechazan su papelería. */ + sendPapeleriaDecision: async ( + to: string, + data: import('./templates/papeleria.js').PapeleriaDecisionData, + ) => { + const { papeleriaDecisionEmail } = await import('./templates/papeleria.js'); + const icon = data.estado === 'aprobado' ? '✅' : '❌'; + await sendEmail( + to, + `${icon} Documento ${data.estado} — ${data.contribuyenteRfc}`, + papeleriaDecisionEmail(data), + ); + }, + + /** + * Cron 8:30 AM — alertas fiscales nuevas activadas hoy. Envía un solo + * correo por destinatario con el batch completo. Caller debe deduplicar + * recipients antes. Una alerta solo se notifica una vez (tracking en + * `alertas_notificadas`). + */ + sendAlertasNuevas: async ( + recipients: string[], + data: import('./templates/alertas-nuevas.js').AlertasNuevasData, + ) => { + if (recipients.length === 0 || data.alertas.length === 0) return; + const { alertasNuevasEmail } = await import('./templates/alertas-nuevas.js'); + const html = alertasNuevasEmail(data); + const total = data.alertas.length; + const subject = `🚨 ${total} alerta${total === 1 ? '' : 's'} nueva${total === 1 ? '' : 's'} — ${data.contribuyenteRfc}`; + for (const to of recipients) { + try { + await sendEmail(to, subject, html); + } catch (err: any) { + console.error(`[Email] Fallo enviando alertas-nuevas a ${to}:`, err?.message || err); + } + } + }, + + /** + * Cron 8:30 AM — recordatorio próximo a vencer. Envía un correo por + * destinatario. Caller dedupea. Cada ventana (3d/1d/0d) se envía a lo más + * una vez por recordatorio (tracking en columnas `email_Xd_at`). + */ + sendRecordatorioProximo: async ( + recipients: string[], + data: import('./templates/recordatorio-proximo.js').RecordatorioProximoData, + ) => { + if (recipients.length === 0) return; + const { recordatorioProximoEmail } = await import('./templates/recordatorio-proximo.js'); + const html = recordatorioProximoEmail(data); + const prefix = data.ventana === '0d' ? '⏰' : data.ventana === '1d' ? '⚠️' : '🗓'; + const ventanaLabel = data.ventana === '0d' ? 'HOY' : data.ventana === '1d' ? 'mañana' : 'en 3 días'; + const subject = `${prefix} Recordatorio ${ventanaLabel}: ${data.titulo}`; + for (const to of recipients) { + try { + await sendEmail(to, subject, html); + } catch (err: any) { + console.error(`[Email] Fallo enviando recordatorio-proximo a ${to}:`, err?.message || err); + } + } + }, +}; diff --git a/apps/api/src/services/email/templates/alertas-nuevas.ts b/apps/api/src/services/email/templates/alertas-nuevas.ts new file mode 100644 index 0000000..ed54949 --- /dev/null +++ b/apps/api/src/services/email/templates/alertas-nuevas.ts @@ -0,0 +1,90 @@ +import { baseTemplate, heading, infoBox, primaryButton, BRAND_COLORS as C } from './base.js'; + +export interface AlertaItem { + /** Identificador interno (e.g., 'lista-negra-propia'). */ + alertaId: string; + /** Nivel: 'high' | 'medium' | 'low'. */ + nivel: 'high' | 'medium' | 'low'; + /** Título corto. */ + titulo: string; + /** Mensaje descriptivo. */ + mensaje: string; +} + +export interface AlertasNuevasData { + /** RFC y nombre del contribuyente al que pertenecen las alertas. */ + contribuyenteRfc: string; + contribuyenteNombre: string; + /** Nombre del despacho. */ + despachoNombre: string; + /** Lista de alertas nuevas (no se reportan resoluciones). */ + alertas: AlertaItem[]; + /** URL al sistema (ej. https://despachos.horuxfin.com/alertas). */ + link: string; +} + +const NIVEL_BADGE: Record = { + high: { label: 'Alta', bg: '#FEE2E2', fg: '#991B1B' }, + medium: { label: 'Media', bg: '#FEF3C7', fg: '#92400E' }, + low: { label: 'Baja', bg: '#DBEAFE', fg: '#1E40AF' }, +}; + +export function alertasNuevasEmail(data: AlertasNuevasData): string { + const total = data.alertas.length; + const conteoNivel = data.alertas.reduce>( + (acc, a) => ({ ...acc, [a.nivel]: (acc[a.nivel] ?? 0) + 1 }), + { high: 0, medium: 0, low: 0 }, + ); + + const itemsHtml = data.alertas.map((a) => alertaItemHtml(a)).join(''); + + return baseTemplate(` + ${heading(`${total} alerta${total === 1 ? '' : 's'} nueva${total === 1 ? '' : 's'}`)} +

+ Detectamos alertas fiscales nuevas para + ${escapeHtml(data.contribuyenteNombre)} + (RFC ${escapeHtml(data.contribuyenteRfc)}) + en el despacho ${escapeHtml(data.despachoNombre)}. +

+ ${infoBox(` +

Resumen

+

+ ${conteoNivel.high > 0 ? `${conteoNivel.high} alta · ` : ''} + ${conteoNivel.medium > 0 ? `${conteoNivel.medium} media · ` : ''} + ${conteoNivel.low > 0 ? `${conteoNivel.low} baja` : ''} +

+ `)} +
+ ${itemsHtml} +
+
+ ${primaryButton('Ver alertas en el sistema', data.link)} +
+

+ Recibes este correo porque eres responsable del contribuyente. Estas alertas + ya fueron registradas — solo te avisaremos cuando aparezcan nuevas, no se + repetirá esta notificación si la misma alerta sigue activa. +

+ `); +} + +function alertaItemHtml(a: AlertaItem): string { + const badge = NIVEL_BADGE[a.nivel]; + return ` +
+
+ ${escapeHtml(a.titulo)} + + ${badge.label} + +
+

${escapeHtml(a.mensaje)}

+
+ `; +} + +function escapeHtml(s: string): string { + return s.replace(/[&<>"']/g, (ch) => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', + })[ch]!); +} diff --git a/apps/api/src/services/email/templates/base.ts b/apps/api/src/services/email/templates/base.ts new file mode 100644 index 0000000..6fa91ee --- /dev/null +++ b/apps/api/src/services/email/templates/base.ts @@ -0,0 +1,113 @@ +/** + * Layout base de todos los emails. Espejea la identidad visual de horux360.com: + * - Gradiente primary→secondary (azul→morado) en el header + * - Tipografías Montserrat (headings) e Inter (body) cargadas vía Google Fonts + * (Apple Mail, Outlook desktop las respetan; Gmail las ignora y cae al + * stack sans-serif del sistema — ambos se ven correctos). + * - Ancho 600px, bordes redondeados, sombra suave + * - Footer en gris claro con color muted del design system + * + * Diseño limitado por restricciones de email HTML: usamos tablas + inline + * styles, nada de flexbox/grid ni CSS externo. Las variables de CSS no + * funcionan (Gmail las ignora), por eso los hex están hardcoded. + */ + +// Tokens del design system de horux360.com — replicados aquí para los emails +const BRAND = { + primary: '#2563EB', + secondary: '#7C3AED', + accent: '#10B981', + textPrimary: '#1E293B', + textMuted: '#64748B', + bgLight: '#F8FAFC', + bgWhite: '#FFFFFF', + border: '#E2E8F0', + gradient: 'linear-gradient(135deg, #2563EB 0%, #7C3AED 100%)', +}; + +// Una sola tipografia (Inter) para mantener consistencia con el design system +// del sitio. Stack fallback cubre los clientes que no soportan webfonts (Gmail). +// IMPORTANTE: usar comillas simples para envolver familias con espacios. +// Comillas dobles dentro de un style="..." rompen el atributo HTML. +const FONT = "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"; + +export function baseTemplate(content: string): string { + const year = new Date().getFullYear(); + return ` + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + +
+ Horux 360 + + Plataforma fiscal inteligente +
+
+ ${content} +
+

+ horux360.com +  ·  + horuxfin.com +

+

© ${year} Horux 360 — Soluciones financieras y tecnológicas para empresas

+
+
+ +`; +} + +/** + * Helper para botón primario consistente con el design system. Usa color + * sólido primary Y gradiente; el cliente que no soporte gradientes cae al + * sólido. Sombra ligera para dar profundidad. + */ +export function primaryButton(label: string, href: string): string { + return `${label}`; +} + +/** Caja destacada para info secundaria (credenciales, totales, etc). */ +export function infoBox(content: string): string { + return `
${content}
`; +} + +/** Heading H2 con tipografía de marca. */ +export function heading(text: string): string { + return `

${text}

`; +} + +export const BRAND_COLORS = BRAND; diff --git a/apps/api/src/services/email/templates/despacho-welcome.ts b/apps/api/src/services/email/templates/despacho-welcome.ts new file mode 100644 index 0000000..ba19419 --- /dev/null +++ b/apps/api/src/services/email/templates/despacho-welcome.ts @@ -0,0 +1,31 @@ +import { baseTemplate, primaryButton, heading } from './base.js'; + +export function despachoWelcomeEmail(data: { + nombre: string; + despachoNombre: string; + email: string; +}): string { + return baseTemplate(` + ${heading('¡Bienvenido a Horux Despachos!')} +

+ Hola ${data.nombre}, +

+

+ Tu despacho ${data.despachoNombre} ha sido creado exitosamente. + Tienes 30 días de prueba gratis para explorar todas las funcionalidades. +

+

+ Próximos pasos: +

+
    +
  1. Agrega tu primer contribuyente (RFC)
  2. +
  3. Sube la FIEL del contribuyente
  4. +
  5. Sube el CSD para emitir facturas
  6. +
  7. Invita a tu equipo (supervisores y auxiliares)
  8. +
+ ${primaryButton('Ir a mi despacho', 'https://horuxfin.com/onboarding')} +

+ Tu cuenta: ${data.email} +

+ `); +} diff --git a/apps/api/src/services/email/templates/documento-subido.ts b/apps/api/src/services/email/templates/documento-subido.ts new file mode 100644 index 0000000..137960d --- /dev/null +++ b/apps/api/src/services/email/templates/documento-subido.ts @@ -0,0 +1,102 @@ +import { baseTemplate, heading, infoBox, primaryButton, BRAND_COLORS as C } from './base.js'; + +export interface DocumentoSubidoData { + /** Kind: para el título/subject. */ + kind: 'declaracion' | 'extra'; + /** Quién subió el documento (email). */ + subidoPor: string; + /** RFC del contribuyente. */ + contribuyenteRfc: string; + /** Razón social / nombre del contribuyente. */ + contribuyenteNombre: string; + /** Nombre del despacho (opcional, se incluye en el body cuando existe). */ + despachoNombre?: string; + /** Si es declaración: periodo + tipo + impuestos + monto. */ + declaracion?: { + periodo: string; // "Abril 2026" + tipo: 'normal' | 'complementaria'; + impuestos: string[]; // ['IVA', 'ISR'] + montoPago: number | null; + }; + /** Si es extra: nombre del documento + categoria. */ + extra?: { + nombre: string; + descripcion?: string | null; + categoria?: string | null; + }; + /** URL al sistema (ej. https://despachos.horuxfin.com/documentos). */ + link: string; +} + +export function documentoSubidoEmail(data: DocumentoSubidoData): string { + const titulo = data.kind === 'declaracion' + ? 'Nueva declaración subida' + : 'Nuevo documento subido'; + + const contenidoEspecifico = data.kind === 'declaracion' && data.declaracion + ? declaracionBlock(data.declaracion) + : data.extra + ? extraBlock(data.extra) + : ''; + + return baseTemplate(` + ${heading(titulo)} +

+ ${escapeHtml(data.subidoPor)} subió un ${data.kind === 'declaracion' ? 'acuse de declaración' : 'documento'} + para ${escapeHtml(data.contribuyenteNombre)}. +

+ ${infoBox(` +

Contribuyente

+

${escapeHtml(data.contribuyenteNombre)}

+

RFC

+

${escapeHtml(data.contribuyenteRfc)}

+ ${contenidoEspecifico} +

Fecha

+

${new Date().toLocaleString('es-MX')}

+ `)} +
+ ${primaryButton('Ver en el sistema', data.link)} +
+ `); +} + +function declaracionBlock(d: NonNullable): string { + const impuestosStr = d.impuestos.join(', '); + const tipoLabel = d.tipo === 'complementaria' ? 'Complementaria' : 'Normal'; + const montoLabel = d.montoPago == null ? '—' : d.montoPago === 0 ? 'Sin pago' : formatCurrency(d.montoPago); + return ` +

Periodo

+

${escapeHtml(d.periodo)}

+

Tipo

+

${tipoLabel}

+

Impuestos

+

${escapeHtml(impuestosStr)}

+

Monto a pagar

+

${montoLabel}

+ `; +} + +function extraBlock(e: NonNullable): string { + return ` +

Documento

+

${escapeHtml(e.nombre)}

+ ${e.categoria ? ` +

Categoría

+

${escapeHtml(e.categoria)}

+ ` : ''} + ${e.descripcion ? ` +

Descripción

+

${escapeHtml(e.descripcion)}

+ ` : ''} + `; +} + +function formatCurrency(n: number): string { + return n.toLocaleString('es-MX', { style: 'currency', currency: 'MXN', minimumFractionDigits: 2 }); +} + +function escapeHtml(s: string): string { + return s.replace(/[&<>"']/g, (ch) => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', + })[ch]!); +} diff --git a/apps/api/src/services/email/templates/fiel-notification.ts b/apps/api/src/services/email/templates/fiel-notification.ts new file mode 100644 index 0000000..58e963f --- /dev/null +++ b/apps/api/src/services/email/templates/fiel-notification.ts @@ -0,0 +1,17 @@ +import { baseTemplate, heading, infoBox, BRAND_COLORS as C } from './base.js'; + +export function fielNotificationEmail(data: { clienteNombre: string; clienteRfc: string }): string { + return baseTemplate(` + ${heading('e.firma cargada')} +

El cliente ${data.clienteNombre} ha subido su e.firma (FIEL).

+ ${infoBox(` +

Empresa

+

${data.clienteNombre}

+

RFC

+

${data.clienteRfc}

+

Fecha

+

${new Date().toLocaleString('es-MX')}

+ `)} +

Ya puedes iniciar la sincronización de CFDIs para este cliente.

+ `); +} diff --git a/apps/api/src/services/email/templates/new-client-admin.ts b/apps/api/src/services/email/templates/new-client-admin.ts new file mode 100644 index 0000000..d9894f8 --- /dev/null +++ b/apps/api/src/services/email/templates/new-client-admin.ts @@ -0,0 +1,58 @@ +import { baseTemplate, heading, BRAND_COLORS as C } from './base.js'; + +function escapeHtml(str: string): string { + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} + +function sectionHeader(label: string, accentColor: string) { + return ` + + ${label} + + `; +} + +function row(label: string, value: string, isLast = false) { + const border = isLast ? '' : `border-bottom:1px solid ${C.border};`; + return ` + ${label} + ${value} + `; +} + +export function newClientAdminEmail(data: { + clienteNombre: string; + clienteRfc: string; + adminEmail: string; + adminNombre: string; + tempPassword: string; + databaseName: string; + plan: string; +}): string { + return baseTemplate(` + ${heading('Nuevo cliente registrado')} +

+ Se ha dado de alta un nuevo cliente en Horux 360. Detalles: +

+ + + ${sectionHeader('Datos del cliente', C.primary)} + ${row('Empresa', `${escapeHtml(data.clienteNombre)}`)} + ${row('RFC', `${escapeHtml(data.clienteRfc)}`)} + ${row('Plan', escapeHtml(data.plan), true)} +
+ + + ${sectionHeader('Credenciales del usuario', C.secondary)} + ${row('Nombre', escapeHtml(data.adminNombre))} + ${row('Email', `${escapeHtml(data.adminEmail)}`)} + ${row('Contraseña temporal', `${escapeHtml(data.tempPassword)}`, true)} +
+ +
+

+ ⚠️ Confidencial: este correo contiene credenciales. No lo reenvíes ni lo compartas. +

+
+ `); +} diff --git a/apps/api/src/services/email/templates/papeleria.ts b/apps/api/src/services/email/templates/papeleria.ts new file mode 100644 index 0000000..580a929 --- /dev/null +++ b/apps/api/src/services/email/templates/papeleria.ts @@ -0,0 +1,57 @@ +import { baseTemplate, primaryButton, infoBox, heading } from './base.js'; + +export interface PapeleriaAprobacionRequeridaData { + contribuyenteRfc: string; + contribuyenteNombre: string; + despachoNombre?: string; + nombreDocumento: string; + descripcion: string | null; + periodo: string; + subidoPor: string; + link: string; +} + +export function papeleriaAprobacionRequeridaEmail(d: PapeleriaAprobacionRequeridaData): string { + const body = ` + ${heading('Papelería pendiente de aprobación')} +

${d.subidoPor} subió un documento de papelería de trabajo que requiere tu aprobación:

+
    +
  • Documento: ${d.nombreDocumento}
  • +
  • Contribuyente: ${d.contribuyenteNombre} (${d.contribuyenteRfc})
  • +
  • Periodo: ${d.periodo}
  • + ${d.descripcion ? `
  • Descripción: ${d.descripcion}
  • ` : ''} +
+ ${infoBox('Revisa el documento y márcalo como aprobado o rechazado desde la sección de Documentos del despacho.')} +
+ ${primaryButton('Ver documento', d.link)} +
+ `; + return baseTemplate(body); +} + +export interface PapeleriaDecisionData { + contribuyenteRfc: string; + contribuyenteNombre: string; + nombreDocumento: string; + estado: 'aprobado' | 'rechazado'; + revisor: string; + comentario: string | null; + periodo: string; + link: string; +} + +export function papeleriaDecisionEmail(d: PapeleriaDecisionData): string { + const verbo = d.estado === 'aprobado' ? 'aprobó' : 'rechazó'; + const body = ` + ${heading(`Documento ${d.estado}`)} +

${d.revisor} ${verbo} el documento ${d.nombreDocumento} + del contribuyente ${d.contribuyenteNombre} (${d.contribuyenteRfc}), periodo ${d.periodo}.

+ ${d.estado === 'rechazado' && d.comentario + ? infoBox(`Comentario: ${d.comentario}`) + : ''} +
+ ${primaryButton('Ver documento', d.link)} +
+ `; + return baseTemplate(body); +} diff --git a/apps/api/src/services/email/templates/password-reset.ts b/apps/api/src/services/email/templates/password-reset.ts new file mode 100644 index 0000000..bfa2da0 --- /dev/null +++ b/apps/api/src/services/email/templates/password-reset.ts @@ -0,0 +1,16 @@ +import { baseTemplate, heading, primaryButton, BRAND_COLORS as C } from './base.js'; + +export function passwordResetEmail(data: { nombre: string; resetUrl: string }): string { + return baseTemplate(` + ${heading('Recuperación de contraseña')} +

Hola ${data.nombre},

+

Recibimos una solicitud para restablecer tu contraseña en Horux 360. Haz clic en el botón para crear una nueva:

+ ${primaryButton('Restablecer contraseña', data.resetUrl)} +

O copia este enlace en tu navegador:

+

${data.resetUrl}

+
+

Este enlace expira en 1 hora.

+

Si tú no solicitaste este cambio, ignora este correo — tu contraseña no cambiará a menos que sigas el enlace.

+
+ `); +} diff --git a/apps/api/src/services/email/templates/payment-confirmed.ts b/apps/api/src/services/email/templates/payment-confirmed.ts new file mode 100644 index 0000000..e98c8b9 --- /dev/null +++ b/apps/api/src/services/email/templates/payment-confirmed.ts @@ -0,0 +1,16 @@ +import { baseTemplate, heading, BRAND_COLORS as C } from './base.js'; + +export function paymentConfirmedEmail(data: { nombre: string; amount: number; plan: string; date: string }): string { + return baseTemplate(` + ${heading('Pago confirmado')} +

Hola ${data.nombre},

+

Hemos recibido tu pago correctamente.

+
+

Monto

+

$${data.amount.toLocaleString('es-MX', { minimumFractionDigits: 2 })} MXN

+

Plan: ${data.plan}

+

Fecha: ${data.date}

+
+

Tu suscripción está activa. Gracias por confiar en Horux 360.

+ `); +} diff --git a/apps/api/src/services/email/templates/payment-failed.ts b/apps/api/src/services/email/templates/payment-failed.ts new file mode 100644 index 0000000..c013782 --- /dev/null +++ b/apps/api/src/services/email/templates/payment-failed.ts @@ -0,0 +1,17 @@ +import { baseTemplate, heading, primaryButton, BRAND_COLORS as C } from './base.js'; + +export function paymentFailedEmail(data: { nombre: string; amount: number; plan: string }): string { + return baseTemplate(` + ${heading('Problema con tu pago')} +

Hola ${data.nombre},

+

No pudimos procesar tu pago. Esto suele deberse a fondos insuficientes, tarjeta vencida o rechazo del banco emisor.

+
+

Monto pendiente

+

$${data.amount.toLocaleString('es-MX', { minimumFractionDigits: 2 })} MXN

+

Plan: ${data.plan}

+
+

Verifica tu método de pago en la plataforma para que tu servicio no se interrumpa:

+ ${primaryButton('Actualizar método de pago', 'https://horuxfin.com/configuracion/suscripcion')} +

Si necesitas ayuda, responde a este correo y nuestro equipo te contactará.

+ `); +} diff --git a/apps/api/src/services/email/templates/recordatorio-proximo.ts b/apps/api/src/services/email/templates/recordatorio-proximo.ts new file mode 100644 index 0000000..edd453e --- /dev/null +++ b/apps/api/src/services/email/templates/recordatorio-proximo.ts @@ -0,0 +1,68 @@ +import { baseTemplate, heading, infoBox, primaryButton, BRAND_COLORS as C } from './base.js'; + +export type VentanaRecordatorio = '3d' | '1d' | '0d'; + +export interface RecordatorioProximoData { + /** Título del recordatorio. */ + titulo: string; + /** Descripción opcional. */ + descripcion?: string | null; + /** Notas opcionales. */ + notas?: string | null; + /** Fecha límite (YYYY-MM-DD). */ + fechaLimite: string; + /** Ventana de aviso: 3d / 1d / 0d. */ + ventana: VentanaRecordatorio; + /** Nombre del despacho. */ + despachoNombre: string; + /** URL al calendario. */ + link: string; +} + +const VENTANA_LABEL: Record = { + '3d': { titulo: 'Recordatorio en 3 días', subtitulo: 'Tienes 3 días para atender este pendiente.', color: '#3B82F6' }, + '1d': { titulo: 'Recordatorio mañana', subtitulo: 'Esto vence mañana — prepara la documentación con tiempo.', color: '#F59E0B' }, + '0d': { titulo: 'Recordatorio HOY', subtitulo: 'Vence hoy. Revisa y atiende cuanto antes.', color: '#EF4444' }, +}; + +export function recordatorioProximoEmail(data: RecordatorioProximoData): string { + const v = VENTANA_LABEL[data.ventana]; + const fechaFormateada = formatFecha(data.fechaLimite); + + return baseTemplate(` + ${heading(v.titulo)} +

${escapeHtml(v.subtitulo)}

+ ${infoBox(` +

Recordatorio

+

${escapeHtml(data.titulo)}

+

Fecha límite

+

${escapeHtml(fechaFormateada)}

+ ${data.descripcion ? ` +

Descripción

+

${escapeHtml(data.descripcion)}

+ ` : ''} + ${data.notas ? ` +

Notas

+

${escapeHtml(data.notas)}

+ ` : ''} +

Despacho

+

${escapeHtml(data.despachoNombre)}

+ `)} +
+ ${primaryButton('Ver en el calendario', data.link)} +
+ `); +} + +function formatFecha(fecha: string): string { + const [y, m, d] = fecha.split('-').map(Number); + if (!y || !m || !d) return fecha; + const dt = new Date(y, m - 1, d); + return dt.toLocaleDateString('es-MX', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); +} + +function escapeHtml(s: string): string { + return s.replace(/[&<>"']/g, (ch) => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', + })[ch]!); +} diff --git a/apps/api/src/services/email/templates/subscription-cancelled.ts b/apps/api/src/services/email/templates/subscription-cancelled.ts new file mode 100644 index 0000000..210d105 --- /dev/null +++ b/apps/api/src/services/email/templates/subscription-cancelled.ts @@ -0,0 +1,15 @@ +import { baseTemplate, heading, primaryButton, infoBox, BRAND_COLORS as C } from './base.js'; + +export function subscriptionCancelledEmail(data: { nombre: string; plan: string }): string { + return baseTemplate(` + ${heading('Suscripción cancelada')} +

Hola ${data.nombre},

+

Tu suscripción al plan ${data.plan} ha sido cancelada.

+ ${infoBox(` +

Tu acceso continuará activo hasta el final del período actual de facturación.

+

Después de eso, solo tendrás acceso de lectura a tus datos.

+ `)} +

¿Cambiaste de opinión? Puedes reactivar tu suscripción antes del cierre del período sin cobro extra:

+ ${primaryButton('Reactivar suscripción', 'https://horuxfin.com/configuracion/suscripcion')} + `); +} diff --git a/apps/api/src/services/email/templates/subscription-expiring.ts b/apps/api/src/services/email/templates/subscription-expiring.ts new file mode 100644 index 0000000..e735e74 --- /dev/null +++ b/apps/api/src/services/email/templates/subscription-expiring.ts @@ -0,0 +1,14 @@ +import { baseTemplate, heading, primaryButton, BRAND_COLORS as C } from './base.js'; + +export function subscriptionExpiringEmail(data: { nombre: string; plan: string; expiresAt: string }): string { + return baseTemplate(` + ${heading('Tu suscripción vence pronto')} +

Hola ${data.nombre},

+

Tu suscripción al plan ${data.plan} vence el ${data.expiresAt}.

+
+

Para evitar interrupciones en el servicio, verifica que tu método de pago esté actualizado.

+
+ ${primaryButton('Revisar suscripción', 'https://horuxfin.com/configuracion/suscripcion')} +

Si tienes alguna pregunta, responde a este correo.

+ `); +} diff --git a/apps/api/src/services/email/templates/tarea-completada.ts b/apps/api/src/services/email/templates/tarea-completada.ts new file mode 100644 index 0000000..a5520b0 --- /dev/null +++ b/apps/api/src/services/email/templates/tarea-completada.ts @@ -0,0 +1,32 @@ +import { baseTemplate, primaryButton, infoBox, heading } from './base.js'; + +export interface TareaCompletadaData { + destinatarioNombre: string; + contribuyenteNombre: string; + contribuyenteRfc: string; + tareaNombre: string; + tareaDescripcion: string | null; + completadaPor: string; + notas: string | null; + fechaLimite: string; + link: string; +} + +export function tareaCompletadaEmail(data: TareaCompletadaData): string { + const body = ` + ${heading(`Tarea revisada: ${data.tareaNombre}`)} +

Hola ${data.destinatarioNombre},

+

+ ${data.completadaPor} marcó como completada la tarea + ${data.tareaNombre} del contribuyente + ${data.contribuyenteNombre} (${data.contribuyenteRfc}), + con fecha límite ${data.fechaLimite}. +

+ ${data.tareaDescripcion ? infoBox(data.tareaDescripcion) : ''} + ${data.notas ? `

Notas del supervisor: ${data.notas}

` : ''} +
+ ${primaryButton('Ver tareas del contribuyente', data.link)} +
+ `; + return baseTemplate(body); +} diff --git a/apps/api/src/services/email/templates/trial-reminder.ts b/apps/api/src/services/email/templates/trial-reminder.ts new file mode 100644 index 0000000..ae108c4 --- /dev/null +++ b/apps/api/src/services/email/templates/trial-reminder.ts @@ -0,0 +1,58 @@ +import { baseTemplate, primaryButton, heading, infoBox } from './base.js'; + +export function trialReminderEmail(data: { + nombre: string; + despachoNombre: string; + diasRestantes: number; + wizardCompleto: boolean; +}): string { + const urgency = data.diasRestantes <= 3; + + const message = data.diasRestantes > 7 + ? `Te quedan ${data.diasRestantes} días de prueba gratuita.` + : urgency + ? `⚠️ Tu prueba gratuita termina en ${data.diasRestantes} días.` + : `Tu prueba gratuita termina en ${data.diasRestantes} días.`; + + const wizardNote = !data.wizardCompleto + ? infoBox('Aún no has completado la configuración inicial de tu despacho. Visita la página de onboarding para terminar.') + : ''; + + return baseTemplate(` + ${heading(urgency ? '⚠️ Tu trial está por terminar' : 'Actualización de tu trial')} +

+ Hola ${data.nombre}, +

+

+ ${message} en ${data.despachoNombre}. +

+ ${wizardNote} +

+ Para seguir usando Horux Despachos sin interrupción, elige un plan antes de que termine tu prueba. +

+ ${primaryButton('Elegir plan', 'https://horuxfin.com/configuracion/suscripcion')} +

+ Si tienes dudas, responde a este correo o contáctanos en soporte@horuxfin.com. +

+ `); +} + +export function trialExpiredEmail(data: { + nombre: string; + despachoNombre: string; +}): string { + return baseTemplate(` + ${heading('Tu prueba gratuita ha terminado')} +

+ Hola ${data.nombre}, +

+

+ El período de prueba de ${data.despachoNombre} ha finalizado. + Tu información sigue segura, pero el acceso está suspendido hasta que elijas un plan. +

+ ${primaryButton('Reactivar mi despacho', 'https://horuxfin.com/configuracion/suscripcion')} +

+ Tus datos se conservarán durante 30 días adicionales. Después de ese período, serán archivados. +

+ `); +} diff --git a/apps/api/src/services/email/templates/weekly-update.ts b/apps/api/src/services/email/templates/weekly-update.ts new file mode 100644 index 0000000..8ef2e83 --- /dev/null +++ b/apps/api/src/services/email/templates/weekly-update.ts @@ -0,0 +1,122 @@ +import { baseTemplate, heading, primaryButton, infoBox, BRAND_COLORS as C } from './base.js'; + +export interface WeeklyUpdateData { + nombre: string; + empresa: string; + periodoLabel: string; // ej: "Abril 2026" + kpis: { + ingresos: number; + egresos: number; + utilidad: number; + margen: number; // porcentaje + ivaBalance: number; // positivo = a pagar, negativo = a favor + ivaAFavorAcumulado: number; + cfdisEmitidos: number; + cfdisRecibidos: number; + }; + alertas: Array<{ + titulo: string; + mensaje: string; + prioridad: 'alta' | 'media' | 'baja'; + }>; + discrepanciasPorMes: Array<{ label: string; count: number }>; + fechaGeneracion: string; +} + +function fmtMoney(n: number): string { + return `$${n.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; +} + +function kpiBox(label: string, value: string, sublabel?: string, color?: string): string { + const borderColor = color || C.border; + const valueColor = color || C.textPrimary; + return ` +
+

${label}

+

${value}

+ ${sublabel ? `

${sublabel}

` : ''} +
+ `; +} + +const PRIORIDAD_COLOR: Record = { + alta: { bg: '#fef2f2', border: '#ef4444', text: '#991b1b' }, + media: { bg: '#fffbeb', border: '#f59e0b', text: '#92400e' }, + baja: { bg: '#eff6ff', border: '#3b82f6', text: '#1e40af' }, +}; + +export function weeklyUpdateEmail(data: WeeklyUpdateData): string { + const { kpis } = data; + const ivaLabel = kpis.ivaBalance >= 0 ? 'IVA a pagar' : 'IVA a favor'; + const ivaValor = Math.abs(kpis.ivaBalance); + const ivaColor = kpis.ivaBalance >= 0 ? '#dc2626' : C.accent; + const utilidadColor = kpis.utilidad >= 0 ? C.accent : '#dc2626'; + + const alertasHtml = data.alertas.length === 0 + ? `
+

✓ No hay alertas activas esta semana. Tu operación está en orden.

+
` + : data.alertas.map(a => { + const colors = PRIORIDAD_COLOR[a.prioridad] || PRIORIDAD_COLOR.baja; + return `
+

${a.titulo}

+

${a.mensaje}

+
`; + }).join(''); + + const discrepanciasHtml = data.discrepanciasPorMes.length === 0 + ? `

Sin discrepancias en los últimos meses. ✓

` + : ` + + + + + + + + ${data.discrepanciasPorMes.map(d => ` + + + `).join('')} + +
MesFacturas con discrepancia
${d.label}${d.count}
`; + + return baseTemplate(` + ${heading('Actualización semanal')} +

Hola ${data.nombre},

+

Aquí tienes el resumen de tu actividad fiscal en ${data.empresa} correspondiente al periodo ${data.periodoLabel}.

+ +

Indicadores del periodo

+ + + ${kpiBox('Ingresos', fmtMoney(kpis.ingresos), `${kpis.cfdisEmitidos} CFDIs emitidos`)} + ${kpiBox('Egresos', fmtMoney(kpis.egresos), `${kpis.cfdisRecibidos} CFDIs recibidos`)} + + + ${kpiBox('Utilidad', fmtMoney(kpis.utilidad), `Margen ${kpis.margen.toFixed(1)}%`, utilidadColor)} + ${kpiBox(ivaLabel, fmtMoney(ivaValor), undefined, ivaColor)} + +
+ + ${kpis.ivaAFavorAcumulado > 0 ? infoBox(` +

IVA a favor acumulado del año

+

${fmtMoney(kpis.ivaAFavorAcumulado)}

+ `) : ''} + +

Alertas activas

+ ${alertasHtml} + +

Discrepancias de régimen por mes

+

Facturas recibidas donde el régimen fiscal del receptor (tu RFC) no coincide con tus regímenes activos. Cada discrepancia podría representar una factura mal emitida que el SAT podría rechazar.

+ ${discrepanciasHtml} + +
+ ${primaryButton('Ver dashboard completo', 'https://horuxfin.com/dashboard')} +
+ +

+ Reporte generado el ${data.fechaGeneracion}.
+ Recibes este correo porque eres dueño o CFO de ${data.empresa} en Horux 360. +

+ `); +} diff --git a/apps/api/src/services/email/templates/welcome.ts b/apps/api/src/services/email/templates/welcome.ts new file mode 100644 index 0000000..279025f --- /dev/null +++ b/apps/api/src/services/email/templates/welcome.ts @@ -0,0 +1,17 @@ +import { baseTemplate, heading, primaryButton, infoBox, BRAND_COLORS as C } from './base.js'; + +export function welcomeEmail(data: { nombre: string; email: string; tempPassword: string }): string { + return baseTemplate(` + ${heading('Bienvenido a Horux 360')} +

Hola ${data.nombre},

+

Tu cuenta ha sido creada exitosamente. Estas son tus credenciales de acceso:

+ ${infoBox(` +

Email

+

${data.email}

+

Contraseña temporal

+

${data.tempPassword}

+ `)} +

Por seguridad, te recomendamos cambiar tu contraseña la primera vez que inicies sesión.

+ ${primaryButton('Iniciar sesión', 'https://horuxfin.com/login')} + `); +} diff --git a/apps/api/src/services/export.service.ts b/apps/api/src/services/export.service.ts new file mode 100644 index 0000000..367dfcc --- /dev/null +++ b/apps/api/src/services/export.service.ts @@ -0,0 +1,125 @@ +import ExcelJS from 'exceljs'; +import type { Pool } from 'pg'; + +export async function exportCfdisToExcel( + pool: Pool, + filters: { tipo?: string; estado?: string; fechaInicio?: string; fechaFin?: string } +): Promise { + let whereClause = 'WHERE 1=1'; + const params: any[] = []; + let paramIndex = 1; + + if (filters.tipo) { + whereClause += ` AND type = $${paramIndex++}`; + params.push(filters.tipo); + } + if (filters.estado) { + whereClause += ` AND status = $${paramIndex++}`; + params.push(filters.estado); + } + if (filters.fechaInicio) { + whereClause += ` AND fecha_emision >= $${paramIndex++}`; + params.push(filters.fechaInicio); + } + if (filters.fechaFin) { + whereClause += ` AND fecha_emision <= $${paramIndex++}`; + params.push(filters.fechaFin); + } + + const { rows: cfdis } = await pool.query(` + SELECT uuid, type, serie, folio, fecha_emision, status, + rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor, + subtotal, subtotal_mxn, descuento, descuento_mxn, + iva_traslado, iva_traslado_mxn, isr_retencion, isr_retencion_mxn, + iva_retencion, iva_retencion_mxn, + total, total_mxn, moneda, tipo_cambio, + metodo_pago, forma_pago, uso_cfdi + FROM cfdis + ${whereClause} + ORDER BY fecha_emision DESC + `, params); + + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet('CFDIs'); + + sheet.columns = [ + { header: 'UUID', key: 'uuid', width: 40 }, + { header: 'Tipo', key: 'type', width: 10 }, + { header: 'Serie', key: 'serie', width: 10 }, + { header: 'Folio', key: 'folio', width: 10 }, + { header: 'Fecha Emisión', key: 'fecha_emision', width: 15 }, + { header: 'RFC Emisor', key: 'rfc_emisor', width: 15 }, + { header: 'Nombre Emisor', key: 'nombre_emisor', width: 30 }, + { header: 'RFC Receptor', key: 'rfc_receptor', width: 15 }, + { header: 'Nombre Receptor', key: 'nombre_receptor', width: 30 }, + { header: 'Subtotal', key: 'subtotal', width: 15 }, + { header: 'Subtotal MXN', key: 'subtotal_mxn', width: 15 }, + { header: 'IVA Trasladado', key: 'iva_traslado', width: 15 }, + { header: 'IVA Trasladado MXN', key: 'iva_traslado_mxn', width: 15 }, + { header: 'Total', key: 'total', width: 15 }, + { header: 'Total MXN', key: 'total_mxn', width: 15 }, + { header: 'Moneda', key: 'moneda', width: 8 }, + { header: 'T.C.', key: 'tipo_cambio', width: 10 }, + { header: 'Estado', key: 'status', width: 12 }, + ]; + + sheet.getRow(1).font = { bold: true }; + sheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FF4472C4' }, + }; + sheet.getRow(1).font = { bold: true, color: { argb: 'FFFFFFFF' } }; + + cfdis.forEach((cfdi: any) => { + sheet.addRow({ + ...cfdi, + fecha_emision: new Date(cfdi.fecha_emision).toLocaleDateString('es-MX'), + subtotal: Number(cfdi.subtotal), + subtotal_mxn: Number(cfdi.subtotal_mxn), + iva_traslado: Number(cfdi.iva_traslado), + iva_traslado_mxn: Number(cfdi.iva_traslado_mxn), + total: Number(cfdi.total), + total_mxn: Number(cfdi.total_mxn), + }); + }); + + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); +} + +export async function exportReporteToExcel( + pool: Pool, + tipo: 'estado-resultados' | 'flujo-efectivo', + fechaInicio: string, + fechaFin: string +): Promise { + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet(tipo === 'estado-resultados' ? 'Estado de Resultados' : 'Flujo de Efectivo'); + + if (tipo === 'estado-resultados') { + const { rows: [totales] } = await pool.query(` + SELECT + COALESCE(SUM(CASE WHEN type = 'EMITIDO' AND tipo_comprobante = 'I' THEN subtotal_mxn ELSE 0 END), 0) as ingresos, + COALESCE(SUM(CASE WHEN type = 'RECIBIDO' AND tipo_comprobante = 'I' THEN subtotal_mxn ELSE 0 END), 0) as egresos + FROM cfdis + WHERE status NOT IN ('Cancelado', '0') AND fecha_emision BETWEEN $1 AND $2 + `, [fechaInicio, fechaFin]); + + sheet.columns = [ + { header: 'Concepto', key: 'concepto', width: 40 }, + { header: 'Monto', key: 'monto', width: 20 }, + ]; + + sheet.addRow({ concepto: 'INGRESOS', monto: '' }); + sheet.addRow({ concepto: 'Total Ingresos', monto: Number(totales?.ingresos || 0) }); + sheet.addRow({ concepto: '', monto: '' }); + sheet.addRow({ concepto: 'EGRESOS', monto: '' }); + sheet.addRow({ concepto: 'Total Egresos', monto: Number(totales?.egresos || 0) }); + sheet.addRow({ concepto: '', monto: '' }); + sheet.addRow({ concepto: 'UTILIDAD NETA', monto: Number(totales?.ingresos || 0) - Number(totales?.egresos || 0) }); + } + + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); +} diff --git a/apps/api/src/services/facturapi.service.ts b/apps/api/src/services/facturapi.service.ts new file mode 100644 index 0000000..a729d52 --- /dev/null +++ b/apps/api/src/services/facturapi.service.ts @@ -0,0 +1,898 @@ +import Facturapi from 'facturapi'; +import { env } from '../config/env.js'; +import { prisma } from '../config/database.js'; +import * as mpService from './payment/mercadopago.service.js'; +import { getTenantOwnerEmail } from '../utils/memberships.js'; +import { encryptString, decryptToString } from './sat/sat-crypto.service.js'; + +/** + * Cliente Facturapi con User Key (nivel cuenta, para gestión de organizaciones). + */ +function getUserClient(): Facturapi { + if (!env.FACTURAPI_USER_KEY) { + throw new Error('FACTURAPI_USER_KEY no configurada'); + } + return new Facturapi(env.FACTURAPI_USER_KEY); +} + +/** + * Genera una Live Secret Key vía PUT idempotente. Devuelve la existente + * si la org ya tiene una; crea nueva si no. + */ +async function generateLiveKey(orgId: string): Promise { + const userKey = env.FACTURAPI_USER_KEY!; + const res = await fetch(`https://www.facturapi.io/v2/organizations/${orgId}/apikeys/live`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${userKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }); + if (!res.ok) { + const errBody = await res.text().catch(() => ''); + throw new Error(`Facturapi PUT /apikeys/live falló (${res.status}): ${errBody}`); + } + const key = (await res.text()).replace(/"/g, '').trim(); + if (!key.startsWith('sk_live_')) { + throw new Error(`Respuesta inesperada de Facturapi (no es sk_live_*): ${key.slice(0, 10)}...`); + } + return key; +} + +/** + * Cliente Facturapi con la Live Secret Key de la organización del tenant. + * Cache cifrada en `Tenant.facturapiOrgKeyEnc/Iv/Tag` (AES-256-GCM con + * derivación FIEL_ENCRYPTION_KEY). Si no hay cache, genera vía PUT y persiste. + */ +async function getOrgClient(tenantId: string): Promise { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { + facturapiOrgId: true, + facturapiOrgKeyEnc: true, + facturapiOrgKeyIv: true, + facturapiOrgKeyTag: true, + }, + }); + + if (!tenant?.facturapiOrgId) { + throw new Error('Tenant no tiene organización Facturapi configurada'); + } + + // 1. Reutilizar Live Secret Key cacheada (descifrar de BD). + if (tenant.facturapiOrgKeyEnc && tenant.facturapiOrgKeyIv && tenant.facturapiOrgKeyTag) { + const apiKey = decryptToString(tenant.facturapiOrgKeyEnc, tenant.facturapiOrgKeyIv, tenant.facturapiOrgKeyTag); + return new Facturapi(apiKey); + } + + // 2. Generar Live Secret Key vía PUT y persistir cifrada (lazy fallback + // para tenants legacy creados antes del refactor live). + const apiKey = await generateLiveKey(tenant.facturapiOrgId); + const { encrypted, iv, tag } = encryptString(apiKey); + await prisma.tenant.update({ + where: { id: tenantId }, + data: { + facturapiOrgKeyEnc: encrypted, + facturapiOrgKeyIv: iv, + facturapiOrgKeyTag: tag, + }, + }); + return new Facturapi(apiKey); +} + +// ============================================ +// Organizaciones +// ============================================ + +export async function createOrganization(tenantId: string): Promise<{ orgId: string }> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { nombre: true, rfc: true, facturapiOrgId: true }, + }); + + if (!tenant) throw new Error('Tenant no encontrado'); + if (tenant.facturapiOrgId) throw new Error('Tenant ya tiene organización Facturapi'); + + const client = getUserClient(); + const org = await client.organizations.create({ name: tenant.nombre }); + + // Eager: generar Live Secret Key inmediatamente y persistirla cifrada + // para que la org quede lista para emitir desde el primer momento sin un + // PUT extra al primer emit. + const apiKey = await generateLiveKey(org.id); + const { encrypted, iv, tag } = encryptString(apiKey); + + await prisma.tenant.update({ + where: { id: tenantId }, + data: { + facturapiOrgId: org.id, + facturapiOrgKeyEnc: encrypted, + facturapiOrgKeyIv: iv, + facturapiOrgKeyTag: tag, + }, + }); + + return { orgId: org.id }; +} + +export async function getOrganizationStatus(tenantId: string): Promise<{ + configured: boolean; + orgId?: string; + legalName?: string; + hasCsd?: boolean; +}> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { facturapiOrgId: true }, + }); + + if (!tenant?.facturapiOrgId) { + return { configured: false }; + } + + try { + const client = getUserClient(); + const org = await client.organizations.retrieve(tenant.facturapiOrgId); + + return { + configured: true, + orgId: org.id, + legalName: org.legal?.name || undefined, + hasCsd: !!org.certificate?.has_certificate, + }; + } catch { + return { configured: false }; + } +} + +// ============================================ +// CSD (Certificado de Sello Digital) +// ============================================ + +export async function uploadCsd( + tenantId: string, + cerFile: string, // base64 + keyFile: string, // base64 + password: string +): Promise<{ success: boolean; message: string }> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { facturapiOrgId: true }, + }); + + if (!tenant?.facturapiOrgId) { + throw new Error('Primero debe crearse la organización en Facturapi'); + } + + const client = getUserClient(); + + try { + await client.organizations.uploadCertificate( + tenant.facturapiOrgId, + Buffer.from(cerFile, 'base64'), + Buffer.from(keyFile, 'base64'), + password, + ); + + return { success: true, message: 'CSD subido correctamente' }; + } catch (error: any) { + return { success: false, message: error.message || 'Error al subir CSD' }; + } +} + +// ============================================ +// Clientes +// ============================================ + +export interface FacturapiCustomerData { + legalName: string; + taxId: string; // RFC o Tax ID extranjero + taxSystem?: string; // clave régimen fiscal (no aplica para extranjeros) + email?: string; + zip: string; + country?: string; // ISO 3166 alpha-3 (solo extranjeros, ej: USA, SWE) +} + +export async function createOrUpdateCustomer( + tenantId: string, + data: FacturapiCustomerData +): Promise { + const client = await getOrgClient(tenantId); + + // Buscar si ya existe por búsqueda de texto + let existingId: string | null = null; + try { + const existing = await client.customers.list({ search: data.taxId }); + if (existing.data && existing.data.length > 0) { + const match = existing.data.find((c: any) => c.tax_id === data.taxId); + if (match) existingId = match.id; + } + } catch { /* no existing */ } + + const isForiegn = !!data.country && data.country !== 'MEX'; + + if (existingId) { + const updateData: any = { + legal_name: data.legalName, + email: data.email, + address: { zip: data.zip, ...(isForiegn ? { country: data.country } : {}) }, + }; + if (!isForiegn && data.taxSystem) updateData.tax_system = data.taxSystem; + await client.customers.update(existingId, updateData); + return existingId; + } + + const createData: any = { + legal_name: data.legalName, + email: data.email, + address: { zip: data.zip, ...(isForiegn ? { country: data.country } : {}) }, + }; + if (isForiegn) { + createData.tax_id = data.taxId; // Tax ID extranjero (NumRegIdTrib) + } else { + createData.tax_id = data.taxId; // RFC mexicano + if (data.taxSystem) createData.tax_system = data.taxSystem; + } + + const customer = await client.customers.create(createData); + + return customer.id; +} + +// ============================================ +// Facturas (Emisión) +// ============================================ + +export interface FacturapiLineItem { + description: string; + productKey: string; // ClaveProdServ SAT + unitKey?: string; // ClaveUnidad SAT + unitName?: string; + quantity: number; + price: number; + taxIncluded?: boolean; + taxes?: Array<{ + type: string; // 'IVA', 'ISR', 'IEPS' + rate: number; // 0.16, 0.10, etc. + factor?: string; // 'Tasa', 'Cuota', 'Exento' + withholding?: boolean; // true = retención, false/undefined = traslado + }>; +} + +export interface FacturapiInvoiceData { + // Receptor + customer: FacturapiCustomerData; + // Conceptos + items: FacturapiLineItem[]; + // Campos CFDI + use: string; // UsoCFDI: G01, G03, etc. + paymentForm: string; // FormaPago: 01, 03, 28, etc. + paymentMethod?: string; // MetodoPago: PUE, PPD + currency?: string; // MXN, USD + exchangeRate?: number; + // Opcionales + series?: string; + folioNumber?: number; + conditions?: string; + /** + * Documentos CFDI relacionados. Estructura SAT 4.0: una entrada por tipo + * de relación, agrupando N UUIDs. Facturapi en modo Live valida la + * estructura estricta — el formato {uuid, relationship} suelto es rechazado. + */ + relatedDocuments?: Array<{ relationship: string; uuids: string[] }>; + /** + * Régimen fiscal del emisor (override del default de la organización). + * Requerido cuando el contribuyente tiene múltiples régimenes y Facturapi + * necesita saber cuál usar para esta factura específica. Se envía como + * `issuer.tax_system` a Facturapi. + */ + issuerTaxSystem?: string; +} + +export async function createInvoice( + tenantId: string, + data: FacturapiInvoiceData +): Promise { + const client = await getOrgClient(tenantId); + + // Crear/actualizar cliente en Facturapi + const customerId = await createOrUpdateCustomer(tenantId, data.customer); + + const tipo = (data as any).type || 'I'; + const invoiceData: any = { customer: customerId }; + + // Tipo de comprobante + if (tipo !== 'I') invoiceData.type = tipo; + + // Items (solo para I, E, T — P no lleva conceptos) + if (data.items?.length) { + invoiceData.items = data.items.map(item => ({ + quantity: item.quantity, + product: { + description: item.description, + product_key: item.productKey, + unit_key: item.unitKey || 'E48', + unit_name: item.unitName || 'Servicio', + price: item.price, + tax_included: item.taxIncluded ?? true, + taxes: item.taxes?.map(t => ({ + type: t.type, + rate: t.rate, + factor: t.factor || 'Tasa', + ...(t.withholding ? { withholding: true } : {}), + })) || [{ type: 'IVA', rate: 0.16 }], + }, + })); + } + + // Campos del comprobante (no aplican para tipo P) + if (tipo === 'I' || tipo === 'E') { + invoiceData.use = data.use || 'G01'; + invoiceData.payment_form = data.paymentForm || '99'; + invoiceData.payment_method = data.paymentMethod || 'PUE'; + invoiceData.currency = data.currency || 'MXN'; + if (data.exchangeRate && data.currency !== 'MXN') { + invoiceData.exchange = data.exchangeRate; + } + if (data.conditions) invoiceData.conditions = data.conditions; + } + + if (data.series) invoiceData.series = data.series; + if (data.folioNumber) invoiceData.folio_number = data.folioNumber; + + // Documentos relacionados (Ingreso / Egreso / Pago / Traslado). + if (data.relatedDocuments?.length) { + invoiceData.related_documents = data.relatedDocuments.map(r => ({ + relationship: r.relationship, + documents: r.uuids, + })); + } + + // Complemento de pago (tipo P) + if ((data as any).complements?.length) { + invoiceData.complements = (data as any).complements; + } + + // Factura global + if ((data as any).global) { + invoiceData.global = (data as any).global; + } + + // El régimen fiscal del emisor lo toma Facturapi del `legal.tax_system` de + // la organización — NO acepta override per-invoice via campo `issuer` (la + // API rechaza con "issuer is not allowed"). Si se pasa `issuerTaxSystem`, + // debe actualizarse el `legal` de la org ANTES de crear el invoice. Para + // el path tenant-level no lo hacemos (la org comparte régimen único); solo + // el path contribuyente (contribuyente-facturapi.service.ts) implementa + // el sync legal porque cada contribuyente tiene sus propios regímenes. + + const invoice = await client.invoices.create(invoiceData); + return invoice; +} + +// ============================================ +// Cancelación +// ============================================ + +// ============================================ +// Personalización (logo, color) +// ============================================ + +export async function uploadLogo(tenantId: string, logoBase64: string): Promise<{ success: boolean; message: string }> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { facturapiOrgId: true }, + }); + if (!tenant?.facturapiOrgId) throw new Error('Organización no configurada'); + + const userClient = getUserClient(); + try { + const buffer = Buffer.from(logoBase64, 'base64'); + await userClient.organizations.uploadLogo(tenant.facturapiOrgId, buffer); + return { success: true, message: 'Logo subido correctamente' }; + } catch (error: any) { + return { success: false, message: error.message || 'Error al subir logo' }; + } +} + +export async function updateColor(tenantId: string, color: string): Promise<{ success: boolean; message: string }> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { facturapiOrgId: true }, + }); + if (!tenant?.facturapiOrgId) throw new Error('Organización no configurada'); + + const userClient = getUserClient(); + try { + await userClient.organizations.updateCustomization(tenant.facturapiOrgId, { color: color.replace('#', '') }); + return { success: true, message: 'Color actualizado' }; + } catch (error: any) { + return { success: false, message: error.message || 'Error al actualizar color' }; + } +} + +export async function getCustomization(tenantId: string): Promise<{ logoUrl?: string; color?: string } | null> { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { facturapiOrgId: true }, + }); + if (!tenant?.facturapiOrgId) return null; + + const userClient = getUserClient(); + try { + const org = await userClient.organizations.retrieve(tenant.facturapiOrgId); + return { + logoUrl: org.customization?.has_logo ? (org.logo_url ?? undefined) : undefined, + color: org.customization?.color || undefined, + }; + } catch { return null; } +} + +export async function sendInvoiceByEmail( + tenantId: string, + facturapiId: string, + email: string +): Promise { + const client = await getOrgClient(tenantId); + await client.invoices.sendByEmail(facturapiId, { email }); +} + +export async function cancelInvoice( + tenantId: string, + facturapiId: string, + motive: '01' | '02' | '03' | '04' = '02', + substitution?: string +): Promise { + const client = await getOrgClient(tenantId); + + const cancelData: any = { motive }; + if (motive === '01' && substitution) { + cancelData.substitution = substitution; + } + + return client.invoices.cancel(facturapiId, cancelData); +} + +// ============================================ +// Descargas +// ============================================ + +export async function downloadPdf(tenantId: string, facturapiId: string): Promise { + const client = await getOrgClient(tenantId); + const stream = await client.invoices.downloadPdf(facturapiId); + return streamToBuffer(stream); +} + +export async function downloadXml(tenantId: string, facturapiId: string): Promise { + const client = await getOrgClient(tenantId); + const stream = await client.invoices.downloadXml(facturapiId); + return streamToBuffer(stream); +} + +export async function downloadZip(tenantId: string, facturapiId: string): Promise { + const client = await getOrgClient(tenantId); + const stream = await client.invoices.downloadZip(facturapiId); + return streamToBuffer(stream); +} + +function streamToBuffer(stream: any): Promise { + return new Promise((resolve, reject) => { + if (Buffer.isBuffer(stream)) return resolve(stream); + const chunks: Buffer[] = []; + stream.on('data', (chunk: Buffer) => chunks.push(chunk)); + stream.on('end', () => resolve(Buffer.concat(chunks))); + stream.on('error', reject); + }); +} + +// ============================================ +// Timbres +// ============================================ + +export interface TimbreStatus { + configured: boolean; + // Campos flat — backward compat con UI existente que lee `limite/usados/disponibles` + // al top level. Representan SOLO el pool mensual (no suman los paquetes). + tipo?: string; + limite?: number; + usados?: number; + disponibles?: number; + periodoFin?: string; + // Shape nuevo (fase B): separa mensual vs adicionales para la UI detallada + mensual?: { + tipo: string; + limite: number; + usados: number; + disponibles: number; + periodoFin: string; + }; + adicionales?: { + total: number; // suma de cantidades originales de paquetes vigentes + usados: number; // suma de usados + disponibles: number; // total - usados + paquetes: Array<{ + id: number; + cantidad: number; + usados: number; + disponibles: number; + adquiridoEn: string; + expiraEn: string; + }>; + }; + /** suma total disponible (mensual + adicionales vigentes). */ + totalDisponibles: number; +} + +export async function getTimbreStatus(tenantId: string): Promise { + const now = new Date(); + + const [suscripcion, paquetes] = await Promise.all([ + prisma.timbreSuscripcion.findUnique({ where: { tenantId } }), + prisma.timbrePaquete.findMany({ + where: { tenantId, expiraEn: { gt: now } }, + orderBy: { expiraEn: 'asc' }, + }), + ]); + + if (!suscripcion && paquetes.length === 0) { + return { configured: false, totalDisponibles: 0 }; + } + + const mensualVigente = suscripcion && now <= suscripcion.periodoFin; + const mensual = mensualVigente + ? { + tipo: suscripcion.tipo, + limite: suscripcion.timbresLimite, + usados: suscripcion.timbresUsados, + disponibles: Math.max(0, suscripcion.timbresLimite - suscripcion.timbresUsados), + periodoFin: suscripcion.periodoFin.toISOString().split('T')[0], + } + : undefined; + + const paquetesDetail = paquetes.map(p => ({ + id: p.id, + cantidad: p.cantidad, + usados: p.usados, + disponibles: Math.max(0, p.cantidad - p.usados), + adquiridoEn: p.adquiridoEn.toISOString(), + expiraEn: p.expiraEn.toISOString(), + })); + + const adicionales = paquetesDetail.length > 0 + ? { + total: paquetesDetail.reduce((s, p) => s + p.cantidad, 0), + usados: paquetesDetail.reduce((s, p) => s + p.usados, 0), + disponibles: paquetesDetail.reduce((s, p) => s + p.disponibles, 0), + paquetes: paquetesDetail, + } + : undefined; + + return { + configured: true, + // backward-compat flat: refleja el mensual si existe, sino deja undefined + tipo: mensual?.tipo, + limite: mensual?.limite, + usados: mensual?.usados, + disponibles: mensual?.disponibles, + periodoFin: mensual?.periodoFin, + // nuevo shape nested + mensual, + adicionales, + totalDisponibles: (mensual?.disponibles || 0) + (adicionales?.disponibles || 0), + }; +} + +/** + * Consume 1 timbre respetando las reglas del feature: + * 1) Intenta contra TimbreSuscripcion si está en periodo vigente y queda cupo. + * 2) Si mensual agotado/vencido, consume del TimbrePaquete con menor expiraEn + * (FIFO para no desperdiciar los próximos a vencer). + * 3) Si no hay nada disponible, lanza error. + * + * La transacción protege contra race conditions en emisiones concurrentes. + */ +export async function consumeTimbre(tenantId: string): Promise<{ source: 'mensual' | 'paquete'; paqueteId?: number }> { + return await prisma.$transaction(async (tx) => { + const now = new Date(); + + // 1) Intenta mensual + const suscripcion = await tx.timbreSuscripcion.findUnique({ where: { tenantId } }); + if (suscripcion && now <= suscripcion.periodoFin && suscripcion.timbresUsados < suscripcion.timbresLimite) { + await tx.timbreSuscripcion.update({ + where: { tenantId }, + data: { timbresUsados: { increment: 1 } }, + }); + return { source: 'mensual' as const }; + } + + // 2) Fallback a paquetes adicionales FIFO por expiraEn + const paquete = await tx.timbrePaquete.findFirst({ + where: { + tenantId, + expiraEn: { gt: now }, + usados: { lt: prisma.timbrePaquete.fields.cantidad }, + }, + orderBy: { expiraEn: 'asc' }, + }); + + if (!paquete) { + // Diferencia los mensajes de error para que el frontend sepa qué ofrecer + if (!suscripcion) { + throw new Error('No hay suscripción de timbres configurada'); + } + if (now > suscripcion.periodoFin) { + throw new Error('La suscripción de timbres ha expirado. Compra timbres adicionales o renueva tu plan.'); + } + throw new Error('Se agotaron los timbres del plan mensual y no tienes paquetes adicionales. Compra un paquete para continuar.'); + } + + await tx.timbrePaquete.update({ + where: { id: paquete.id }, + data: { usados: { increment: 1 } }, + }); + + return { source: 'paquete' as const, paqueteId: paquete.id }; + }); +} + +/** + * Revierte un consumo previo de timbre. Idempotente por fuente: + * - mensual → decrementa timbresUsados (no baja de 0 gracias al guard) + * - paquete → decrementa usados del paquete específico (mismo guard) + * + * Se invoca cuando la emisión en Facturapi falla después de haber consumido + * (SAT nunca selló → el timbre no debe cobrarse). + */ +export async function refundTimbre( + tenantId: string, + consumed: { source: 'mensual' | 'paquete'; paqueteId?: number }, +): Promise { + await prisma.$transaction(async (tx) => { + if (consumed.source === 'mensual') { + const sub = await tx.timbreSuscripcion.findUnique({ where: { tenantId } }); + if (sub && sub.timbresUsados > 0) { + await tx.timbreSuscripcion.update({ + where: { tenantId }, + data: { timbresUsados: { decrement: 1 } }, + }); + } + return; + } + if (consumed.source === 'paquete' && consumed.paqueteId != null) { + const pkg = await tx.timbrePaquete.findUnique({ where: { id: consumed.paqueteId } }); + if (pkg && pkg.usados > 0) { + await tx.timbrePaquete.update({ + where: { id: consumed.paqueteId }, + data: { usados: { decrement: 1 } }, + }); + } + } + }); +} + +/** + * Reset mensual de TimbreSuscripcion: para cada tenant cuyo periodoFin ya pasó, + * resetea `timbresUsados=0` y avanza la ventana un mes (tipo='mensual') o un año + * (tipo='anual'). Usado por cron diario. Idempotente: si no hay vencidas, no-op. + * + * Los paquetes adicionales NO se tocan aquí — su vigencia es 1 año fijo desde + * la compra y el filtro `expiraEn > now` los excluye automáticamente cuando + * caducan. + */ +export async function resetExpiredMonthlyTimbres(): Promise<{ reset: number }> { + const now = new Date(); + const vencidas = await prisma.timbreSuscripcion.findMany({ + where: { periodoFin: { lt: now } }, + }); + + let count = 0; + for (const s of vencidas) { + const nextInicio = new Date(s.periodoFin); + nextInicio.setDate(nextInicio.getDate() + 1); + const nextFin = new Date(nextInicio); + if (s.tipo === 'anual') { + nextFin.setFullYear(nextFin.getFullYear() + 1); + nextFin.setDate(nextFin.getDate() - 1); + } else { + nextFin.setMonth(nextFin.getMonth() + 1); + nextFin.setDate(nextFin.getDate() - 1); + } + + await prisma.timbreSuscripcion.update({ + where: { id: s.id }, + data: { + timbresUsados: 0, + periodoInicio: nextInicio, + periodoFin: nextFin, + }, + }); + count++; + console.log(`[Timbres] Reset mensual tenant ${s.tenantId}: nuevo periodo ${nextInicio.toISOString().split('T')[0]} → ${nextFin.toISOString().split('T')[0]}`); + } + + return { reset: count }; +} + +// ============================================ +// Paquetes adicionales: catálogo + compra + activación +// ============================================ + +/** Lista los paquetes activos del catálogo, ordenados por cantidad ASC. */ +export async function listPaquetesCatalogo() { + const rows = await prisma.timbrePaqueteCatalogo.findMany({ + where: { active: true }, + orderBy: { cantidad: 'asc' }, + }); + return rows.map(r => ({ + id: r.id, + cantidad: r.cantidad, + precio: Number(r.precio), + })); +} + +/** + * Lista todos los paquetes del catálogo (incluyendo inactivos). Para admin + * global que edita precios — necesita ver los dados de baja también. + */ +export async function listAllPaquetesCatalogo() { + const rows = await prisma.timbrePaqueteCatalogo.findMany({ + orderBy: { cantidad: 'asc' }, + }); + return rows.map(r => ({ + id: r.id, + cantidad: r.cantidad, + precio: Number(r.precio), + active: r.active, + updatedAt: r.updatedAt.toISOString(), + })); +} + +/** + * Actualiza precio y/o estado activo de un paquete del catálogo. Solo admin + * global. Los cambios NO afectan paquetes ya vendidos (TimbrePaquete guarda + * snapshot del precio al momento de compra). + */ +export async function updatePaqueteCatalogo(params: { + id: number; + precio?: number; + active?: boolean; +}) { + const data: { precio?: number; active?: boolean } = {}; + if (params.precio !== undefined) { + if (params.precio <= 0) throw new Error('El precio debe ser mayor a 0'); + data.precio = params.precio; + } + if (params.active !== undefined) data.active = params.active; + + if (Object.keys(data).length === 0) { + throw new Error('Nada que actualizar'); + } + + const updated = await prisma.timbrePaqueteCatalogo.update({ + where: { id: params.id }, + data, + }); + + return { + id: updated.id, + cantidad: updated.cantidad, + precio: Number(updated.precio), + active: updated.active, + updatedAt: updated.updatedAt.toISOString(), + }; +} + +/** + * Inicia la compra de un paquete. Crea un Payment con status=pending y una + * MP Preference (checkout one-shot). Retorna la URL a la que redirigir al user. + * + * Flujo post-pago: + * 1. User paga en MP + * 2. MP dispara webhook `payment.approved` con external_reference = `timbres-pack:{paymentId}` + * 3. `applyApprovedTimbrePack` (fase webhook) crea el TimbrePaquete y emite factura + */ +export async function iniciarCompraPaquete(params: { + tenantId: string; + catalogoId: number; + callerEmail: string; // email del user que inicia la compra (caller) +}): Promise<{ paymentId: string; checkoutUrl: string }> { + const paquete = await prisma.timbrePaqueteCatalogo.findUnique({ + where: { id: params.catalogoId }, + }); + if (!paquete || !paquete.active) { + throw new Error('Paquete no disponible'); + } + + // Email de pago: preferencia al owner del tenant (para continuidad del flujo + // normal de facturación). Si no hay owner activo (caso edge: tenant sin + // membership owner por ahora), caemos al email del caller para no bloquear. + const ownerEmail = await getTenantOwnerEmail(params.tenantId); + const payerEmail = ownerEmail || params.callerEmail; + if (!payerEmail) { + throw new Error('No se pudo determinar un email para el cobro'); + } + + // Payment pre-creado como pending. El webhook lo aprueba. Guardamos cantidad + // en un campo que podamos recuperar — usamos paymentMethod como placeholder + // para carry-over del cantidad mientras no haya un campo metadata dedicado. + // Mejor: el paqueteId del catálogo es único y persistente, no hace falta + // guardar cantidad aparte. + const payment = await prisma.payment.create({ + data: { + tenantId: params.tenantId, + amount: paquete.precio, + status: 'pending', + kind: 'timbres_pack', + paymentMethod: `catalogo:${paquete.id}`, // marker para recuperar cantidad en webhook + }, + }); + + const { preferenceId, checkoutUrl } = await mpService.createTimbrePackPreference({ + paymentId: payment.id, + tenantId: params.tenantId, + cantidad: paquete.cantidad, + amount: Number(paquete.precio), + payerEmail, + }); + + // Guardamos el preferenceId por si lo necesitamos para debugging o cancel + await prisma.payment.update({ + where: { id: payment.id }, + data: { mpPaymentId: preferenceId }, // temporal hasta que llegue el paymentId real + }); + + return { paymentId: payment.id, checkoutUrl }; +} + +/** + * Activa el paquete una vez que MP confirmó el pago. Idempotente: si ya hay un + * TimbrePaquete para este paymentId, no-op. + * + * Llamado desde el webhook cuando external_reference = timbres-pack:{paymentId} + * Y status=approved. + */ +export async function activarPaqueteTrasPago(paymentId: string): Promise<{ created: boolean; paqueteId?: number }> { + const existing = await prisma.timbrePaquete.findUnique({ + where: { paymentId }, + }); + if (existing) { + console.log(`[Timbres] Paquete ya activado para payment ${paymentId} (idempotente)`); + return { created: false, paqueteId: existing.id }; + } + + const payment = await prisma.payment.findUnique({ where: { id: paymentId } }); + if (!payment) throw new Error(`Payment ${paymentId} no encontrado`); + if (payment.kind !== 'timbres_pack') { + throw new Error(`Payment ${paymentId} no es de tipo timbres_pack`); + } + + // Recupera cantidad del marker paymentMethod (catalogo:ID) + const match = /^catalogo:(\d+)$/.exec(payment.paymentMethod || ''); + if (!match) { + throw new Error(`Payment ${paymentId} sin marker de catálogo válido`); + } + const catalogoId = Number(match[1]); + const catalogo = await prisma.timbrePaqueteCatalogo.findUnique({ where: { id: catalogoId } }); + if (!catalogo) { + throw new Error(`Catálogo ${catalogoId} referenciado por Payment ${paymentId} ya no existe`); + } + + const adquiridoEn = new Date(); + const expiraEn = new Date(adquiridoEn); + expiraEn.setFullYear(expiraEn.getFullYear() + 1); + + const paquete = await prisma.timbrePaquete.create({ + data: { + tenantId: payment.tenantId, + paymentId: payment.id, + cantidad: catalogo.cantidad, + precio: payment.amount, // precio pagado (historial, snapshot del momento) + adquiridoEn, + expiraEn, + }, + }); + + console.log(`[Timbres] Activado paquete ${paquete.id} (${catalogo.cantidad} timbres) para tenant ${payment.tenantId}, expira ${expiraEn.toISOString().split('T')[0]}`); + return { created: true, paqueteId: paquete.id }; +} diff --git a/apps/api/src/services/fiel.service.ts b/apps/api/src/services/fiel.service.ts new file mode 100644 index 0000000..b16c35f --- /dev/null +++ b/apps/api/src/services/fiel.service.ts @@ -0,0 +1,313 @@ +import { Credential } from '@nodecfdi/credentials/node'; +import { writeFile, mkdir } from 'fs/promises'; +import { join } from 'path'; +import { prisma } from '../config/database.js'; +import { env } from '../config/env.js'; +import { encryptFielCredentials, encrypt, decryptFielCredentials } from './sat/sat-crypto.service.js'; +import { emailService } from './email/email.service.js'; +import type { FielStatus } from '@horux/shared'; + +/** + * Sube y valida credenciales FIEL + */ +export async function uploadFiel( + tenantId: string, + cerBase64: string, + keyBase64: string, + password: string +): Promise<{ success: boolean; message: string; status?: FielStatus }> { + try { + // Decodificar archivos de Base64 + const cerData = Buffer.from(cerBase64, 'base64'); + const keyData = Buffer.from(keyBase64, 'base64'); + + // Validar que los archivos sean válidos y coincidan + let credential: Credential; + try { + credential = Credential.create( + cerData.toString('binary'), + keyData.toString('binary'), + password + ); + } catch (error: any) { + return { + success: false, + message: 'Los archivos de la FIEL no son válidos o la contraseña es incorrecta', + }; + } + + // Verificar que sea una FIEL (no CSD) + if (!credential.isFiel()) { + return { + success: false, + message: 'El certificado proporcionado no es una FIEL (e.firma). Parece ser un CSD.', + }; + } + + // Obtener información del certificado + const certificate = credential.certificate(); + const rfc = certificate.rfc(); + const serialNumber = certificate.serialNumber().bytes(); + // validFromDateTime() y validToDateTime() retornan strings ISO o objetos DateTime + const validFromRaw = certificate.validFromDateTime(); + const validUntilRaw = certificate.validToDateTime(); + const validFrom = new Date(String(validFromRaw)); + const validUntil = new Date(String(validUntilRaw)); + + // Verificar que no esté vencida + if (new Date() > validUntil) { + return { + success: false, + message: 'La FIEL está vencida desde ' + validUntil.toLocaleDateString(), + }; + } + + // Encriptar credenciales (per-component IV/tag) + const { + encryptedCer, + encryptedKey, + encryptedPassword, + cerIv, + cerTag, + keyIv, + keyTag, + passwordIv, + passwordTag, + } = encryptFielCredentials(cerData, keyData, password); + + // Detectar si es la primera subida (no existe fielCredential previo activo) + // — se usa abajo para disparar Opinión de Cumplimiento + CSF iniciales. + const existingFiel = await prisma.fielCredential.findUnique({ + where: { tenantId }, + select: { isActive: true }, + }); + const esPrimeraSubida = !existingFiel || !existingFiel.isActive; + + // Guardar o actualizar en BD + await prisma.fielCredential.upsert({ + where: { tenantId }, + create: { + tenantId, + rfc, + cerData: encryptedCer, + keyData: encryptedKey, + keyPasswordEncrypted: encryptedPassword, + cerIv, + cerTag, + keyIv, + keyTag, + passwordIv, + passwordTag, + serialNumber, + validFrom, + validUntil, + isActive: true, + }, + update: { + rfc, + cerData: encryptedCer, + keyData: encryptedKey, + keyPasswordEncrypted: encryptedPassword, + cerIv, + cerTag, + keyIv, + keyTag, + passwordIv, + passwordTag, + serialNumber, + validFrom, + validUntil, + isActive: true, + updatedAt: new Date(), + }, + }); + + // Save encrypted files to filesystem (dual storage) + try { + const fielDir = join(env.FIEL_STORAGE_PATH, rfc.toUpperCase()); + await mkdir(fielDir, { recursive: true, mode: 0o700 }); + + // Re-encrypt for filesystem (independent keys from DB) + const fsEncrypted = encryptFielCredentials(cerData, keyData, password); + + await writeFile(join(fielDir, 'certificate.cer.enc'), fsEncrypted.encryptedCer, { mode: 0o600 }); + await writeFile(join(fielDir, 'certificate.cer.iv'), fsEncrypted.cerIv, { mode: 0o600 }); + await writeFile(join(fielDir, 'certificate.cer.tag'), fsEncrypted.cerTag, { mode: 0o600 }); + await writeFile(join(fielDir, 'private_key.key.enc'), fsEncrypted.encryptedKey, { mode: 0o600 }); + await writeFile(join(fielDir, 'private_key.key.iv'), fsEncrypted.keyIv, { mode: 0o600 }); + await writeFile(join(fielDir, 'private_key.key.tag'), fsEncrypted.keyTag, { mode: 0o600 }); + + // Encrypt and store metadata + const metadata = JSON.stringify({ + serial: serialNumber, + validFrom: validFrom.toISOString(), + validUntil: validUntil.toISOString(), + uploadedAt: new Date().toISOString(), + rfc: rfc.toUpperCase(), + }); + const metaEncrypted = encrypt(Buffer.from(metadata, 'utf-8')); + await writeFile(join(fielDir, 'metadata.json.enc'), metaEncrypted.encrypted, { mode: 0o600 }); + await writeFile(join(fielDir, 'metadata.json.iv'), metaEncrypted.iv, { mode: 0o600 }); + await writeFile(join(fielDir, 'metadata.json.tag'), metaEncrypted.tag, { mode: 0o600 }); + } catch (fsError) { + console.error('[FIEL] Filesystem storage failed (DB storage OK):', fsError); + } + + // Notify admin that client uploaded FIEL + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { nombre: true, rfc: true }, + }); + if (tenant) { + emailService.sendFielNotification({ + clienteNombre: tenant.nombre, + clienteRfc: tenant.rfc, + }).catch(err => console.error('[EMAIL] FIEL notification failed:', err)); + } + + // Al primer upload de FIEL, disparar Opinión de Cumplimiento + CSF en + // background. Fire-and-forget — no bloqueamos la respuesta porque ambos + // procesos abren Playwright y tardan minutos. La CSF además autocompleta + // domicilio y regímenes activos del tenant. + if (esPrimeraSubida) { + import('./opinion-cumplimiento.service.js').then(({ consultarOpinion }) => + consultarOpinion(tenantId), + ).catch(err => console.error(`[FIEL first-upload] Opinión falló para tenant ${tenantId}:`, err.message || err)); + + import('./constancia.service.js').then(({ consultarConstancia }) => + consultarConstancia(tenantId), + ).catch(err => console.error(`[FIEL first-upload] CSF falló para tenant ${tenantId}:`, err.message || err)); + } + + const daysUntilExpiration = Math.ceil( + (validUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24) + ); + + return { + success: true, + message: 'FIEL configurada correctamente', + status: { + configured: true, + rfc, + serialNumber, + validFrom: validFrom.toISOString(), + validUntil: validUntil.toISOString(), + isExpired: false, + daysUntilExpiration, + }, + }; + } catch (error: any) { + console.error('[FIEL Upload Error]', error); + return { + success: false, + message: error.message || 'Error al procesar la FIEL', + }; + } +} + +/** + * Obtiene el estado de la FIEL de un tenant + */ +export async function getFielStatus(tenantId: string): Promise { + const fiel = await prisma.fielCredential.findUnique({ + where: { tenantId }, + select: { + rfc: true, + serialNumber: true, + validFrom: true, + validUntil: true, + isActive: true, + }, + }); + + if (!fiel || !fiel.isActive) { + return { configured: false }; + } + + const now = new Date(); + const isExpired = now > fiel.validUntil; + const daysUntilExpiration = Math.ceil( + (fiel.validUntil.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) + ); + + return { + configured: true, + rfc: fiel.rfc, + serialNumber: fiel.serialNumber || undefined, + validFrom: fiel.validFrom.toISOString(), + validUntil: fiel.validUntil.toISOString(), + isExpired, + daysUntilExpiration: isExpired ? 0 : daysUntilExpiration, + }; +} + +/** + * Elimina la FIEL de un tenant + */ +export async function deleteFiel(tenantId: string): Promise { + try { + await prisma.fielCredential.delete({ + where: { tenantId }, + }); + return true; + } catch { + return false; + } +} + +/** + * Obtiene las credenciales desencriptadas para usar en sincronización + * Solo debe usarse internamente por el servicio de SAT + */ +export async function getDecryptedFiel(tenantId: string): Promise<{ + cerContent: string; + keyContent: string; + password: string; + rfc: string; +} | null> { + const fiel = await prisma.fielCredential.findUnique({ + where: { tenantId }, + }); + + if (!fiel || !fiel.isActive) { + return null; + } + + // Verificar que no esté vencida + if (new Date() > fiel.validUntil) { + return null; + } + + try { + // Desencriptar credenciales (per-component IV/tag) + const { cerData, keyData, password } = decryptFielCredentials( + Buffer.from(fiel.cerData), + Buffer.from(fiel.keyData), + Buffer.from(fiel.keyPasswordEncrypted), + Buffer.from(fiel.cerIv), + Buffer.from(fiel.cerTag), + Buffer.from(fiel.keyIv), + Buffer.from(fiel.keyTag), + Buffer.from(fiel.passwordIv), + Buffer.from(fiel.passwordTag) + ); + + return { + cerContent: cerData.toString('binary'), + keyContent: keyData.toString('binary'), + password, + rfc: fiel.rfc, + }; + } catch (error) { + console.error('[FIEL Decrypt Error]', error); + return null; + } +} + +/** + * Verifica si un tenant tiene FIEL configurada y válida + */ +export async function hasFielConfigured(tenantId: string): Promise { + const status = await getFielStatus(tenantId); + return status.configured && !status.isExpired; +} diff --git a/apps/api/src/services/impuestos.service.ts b/apps/api/src/services/impuestos.service.ts new file mode 100644 index 0000000..446df7e --- /dev/null +++ b/apps/api/src/services/impuestos.service.ts @@ -0,0 +1,1150 @@ +import type { Pool } from 'pg'; +import type { IvaMensual, IsrMensual, ResumenIva, IvaRegimenDetalle, ResumenIsr } from '@horux/shared'; +import { getRegimenesIgnoradosClaves } from './regimen.service.js'; +import { + calcularIngresosPorRegimen, + calcularEgresosPorRegimen, + calcularNcsEmitidasPorRegimen, + calcularNcsRecibidasPorRegimen, + calcularGastosNoDeduciblesEfectivoPorRegimen, + calcularIvaNoAcreditableEfectivoPorRegimen, + NO_DEDUCIBLE_EFECTIVO_I_PUE, + NO_DEDUCIBLE_EFECTIVO_P, +} from './dashboard.service.js'; +import { prisma } from '../config/database.js'; +import { resolveContribuyenteContext } from '../utils/contribuyente-context.js'; +import { planCache, type CacheRange } from '../utils/metricas-cache.js'; +import { buildExtraFilters, buildExtraFiltersAlias } from './_shared/cfdi-filters.js'; + +const VIGENTE = `status NOT IN ('Cancelado', '0')`; +// Para cálculos de IVA/ISR la fecha efectiva depende del tipo de comprobante: +// - tipo P (complemento de pago): fecha real del cobro (fecha_pago_p) +// - otros tipos (I, E, T, N): fecha_emision del comprobante +// El CASE se evalúa por fila, garantizando que un P emitido en mayo por un pago +// real de noviembre quede contabilizado en noviembre. +const FECHA_EFECTIVA = `CASE WHEN tipo_comprobante = 'P' THEN fecha_pago_p ELSE fecha_emision END`; +const FECHA_RANGO = `${FECHA_EFECTIVA} >= $1::date AND ${FECHA_EFECTIVA} < ($2::date + interval '1 day')`; +const FECHA_RANGO_CONCILIACION = `id_conciliacion IS NOT NULL AND id_conciliacion IN ( + SELECT id FROM conciliaciones WHERE fecha_de_pago >= $1::date AND fecha_de_pago < ($2::date + interval '1 day') +)`; +function getFR(conciliacion?: boolean): string { + return conciliacion ? FECHA_RANGO_CONCILIACION : FECHA_RANGO; +} +const TODOS_REGIMENES = ['605', '606', '612', '621', '625', '626', '601', '603', '607', '608', '610', '611', '614', '615', '620', '622', '623', '624']; + +// Claves de producto/servicio excluidas de cálculos fiscales +const CLAVES_EXCLUIDAS = `('84121603','93161608','85101501','85121800')`; +const EXCL_IVA_TRAS = `COALESCE((SELECT SUM(COALESCE(cc.iva_traslado_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; +const EXCL_IVA_RET = `COALESCE((SELECT SUM(COALESCE(cc.iva_retencion_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; +const EXCL_ISR_RET = `COALESCE((SELECT SUM(COALESCE(cc.isr_retencion_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; + +// Fragmentos IVA: CFDIs tipo P usan campos `_pago_mxn` directos; los demás +// tipos usan los campos base del CFDI restando IVA de conceptos excluidos +// (claves prod/serv 84121603, 93161608, 85101501, 85121800). +// +// Los campos `_pago_mxn` se usan tal cual sin clamp — el spec del usuario +// los toma directos. (El clamp `LEAST(iva, monto*0.16)` defendía contra XMLs +// que reportaban IVA de la factura completa en P parciales; se removió a +// petición del owner; ver doc 2026-04-26-iva-refactor.md.) +const IVA_TRAS_EXPR = `CASE WHEN tipo_comprobante = 'P' + THEN COALESCE(iva_traslado_pago_mxn, 0) + ELSE COALESCE(iva_traslado_mxn, 0) - (${EXCL_IVA_TRAS}) +END`; +const IVA_RET_EXPR = `CASE WHEN tipo_comprobante = 'P' + THEN COALESCE(iva_retencion_pago_mxn, 0) + ELSE COALESCE(iva_retencion_mxn, 0) - (${EXCL_IVA_RET}) +END`; + +// Versiones parametrizadas por alias — usadas en subqueries que resuelven +// las E que cancelan I PPD/07 (rama nueva). Mismo tratamiento sin clamp. +const EXCL_IVA_TRAS_ALIAS = (alias: string) => + `COALESCE((SELECT SUM(COALESCE(cc.iva_traslado_mxn, 0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = ${alias}.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; +const EXCL_IVA_RET_ALIAS = (alias: string) => + `COALESCE((SELECT SUM(COALESCE(cc.iva_retencion_mxn, 0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = ${alias}.id AND cc.clave_prod_serv IN ${CLAVES_EXCLUIDAS}), 0)`; +const IVA_TRAS_EXPR_ALIAS = (alias: string) => `CASE WHEN ${alias}.tipo_comprobante = 'P' + THEN COALESCE(${alias}.iva_traslado_pago_mxn, 0) + ELSE COALESCE(${alias}.iva_traslado_mxn, 0) - (${EXCL_IVA_TRAS_ALIAS(alias)}) +END`; +const IVA_RET_EXPR_ALIAS = (alias: string) => `CASE WHEN ${alias}.tipo_comprobante = 'P' + THEN COALESCE(${alias}.iva_retencion_pago_mxn, 0) + ELSE COALESCE(${alias}.iva_retencion_mxn, 0) - (${EXCL_IVA_RET_ALIAS(alias)}) +END`; + +/** + * Condición que identifica I PPD con TipoRelacion=07 (aplicación de anticipo + * en operación PPD). La PPD no aporta IVA en su mes de emisión (se causa al + * cobrar via tipo P). Cuando existe una E del mismo mes/año que la referencia + * y la cancela, la E resta IVA en su mes pero la I PPD/07 no aportó nada que + * compensar. La regla (mirror de `i07PpdComp` en dashboard.service.ts) es + * darle a la I PPD/07 el IVA de la E que la cancela, así I PPD + E netean a 0 + * dentro del mes. + */ +const IS_I_PPD_07 = `(tipo_comprobante = 'I' AND metodo_pago = 'PPD' AND COALESCE(cfdi_tipo_relacion, '') = '07')`; + +/** + * Subqueries que suman el IVA_TRAS (ó IVA_RET) de las E del **mismo lado** + * (emisor o receptor del contribuyente) que referencian este CFDI (la I PPD/07) + * en `cfdis_relacionados` y caen en el **mismo mes/año**. Aplicado a I PPD/07 + * via las ramas nuevas en signed exprs: la I PPD/07 hereda como aporte el + * IVA de TODAS las E que la cancelan (sin importar su tipoRelación), + * igualando lo que esas mismas E restan en NEG. + * + * No filtra por tipoRelación: en PPD, las E/07 que referencian la I PPD/07 + * SÍ entran al NEG (vía la condición `E_REFERENCIA_I_PPD_07_MISMO_MES` agregada + * al `bucketCausadoNeg`/`bucketAcreditableNeg`). Como ambas patas — la E + * resta en NEG y la I PPD hereda — usan el mismo set de E's, los IVAs se + * cancelan exactamente dentro del mes. Esto es distinto al triángulo PUE + * (anticipo + I PUE/07 + E/07) donde la E/07 sigue excluida del NEG porque + * apunta al anticipo, no a una I PPD/07. + * + * `esLadoE` es la cláusula `UPPER(rfc_emisor)='X'` o `UPPER(rfc_receptor)='X'` + * traducida al alias `e` (el caller hace el rewrite). Filtrar por mismo + * lado evita capturar E's de otros contribuyentes del tenant que casualmente + * referencien el mismo UUID. + */ +const SUM_E_REFERENCING_TRAS = ( + esLadoE: string, + considerarActivos: boolean, + considerarNCs: boolean, +) => `COALESCE(( + SELECT SUM(${IVA_TRAS_EXPR_ALIAS('e')}) + FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND e.metodo_pago = 'PUE' + AND e.status NOT IN ('Cancelado', '0') + AND ${esLadoE} + AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND date_trunc('month', e.fecha_emision) + = date_trunc('month', cfdis.fecha_emision)${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)} +), 0)`; +const SUM_E_REFERENCING_RET = ( + esLadoE: string, + considerarActivos: boolean, + considerarNCs: boolean, +) => `COALESCE(( + SELECT SUM(${IVA_RET_EXPR_ALIAS('e')}) + FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND e.metodo_pago = 'PUE' + AND e.status NOT IN ('Cancelado', '0') + AND ${esLadoE} + AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND date_trunc('month', e.fecha_emision) + = date_trunc('month', cfdis.fecha_emision)${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)} +), 0)`; +// Régimen del contribuyente según su lado: emisor/receptor del CFDI. +// Usa el RFC del contribuyente (via `ctx.esEmisor`/`ctx.esReceptor`) para +// determinar el lado, no el `type` de BD. +const regimenTenantExpr = (ctx: { esEmisor: string; esReceptor: string }) => + `CASE WHEN ${ctx.esEmisor} THEN regimen_fiscal_emisor + WHEN ${ctx.esReceptor} THEN regimen_fiscal_receptor + ELSE NULL END`; + +/** + * Predicado EXISTS que detecta si el CFDI actual (alias implícito `cfdis`) es + * referenciado en `cfdis_relacionados` por al menos una E del **mismo lado** + * y **mismo mes/año**. Usado para incluir I PPD/07 en los buckets Any — sin + * esto, las I PPD/07 quedan fuera del WHERE y las ramas nuevas en signed + * exprs nunca se evalúan. No filtra tipoRelación: en PPD cualquier E que + * referencie la I PPD/07 cuenta (incluyendo las 07, fiscalmente correctas). + */ +const HAS_E_REFERENCING_MISMO_MES = ( + esLadoE: string, + considerarActivos: boolean, + considerarNCs: boolean, +) => `EXISTS ( + SELECT 1 FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND e.metodo_pago = 'PUE' + AND e.status NOT IN ('Cancelado', '0') + AND ${esLadoE} + AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND date_trunc('month', e.fecha_emision) + = date_trunc('month', cfdis.fecha_emision)${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)} +)`; + +// Atribución por lado usando RFC en lugar de `type`. Los buckets son +// factories que reciben el context del contribuyente: +// POS: CFDIs que suman (I PUE + P del lado correcto) +// NEG: NC del mismo lado (todas las E PUE, sin filtrar TipoRelación — +// las E/07 también restan, en línea con la lógica del owner que asume +// que el contador emite la E/07 cuando aplica el anticipo). +// Queries deben sumar signed: CASE WHEN POS THEN +X WHEN NEG THEN -X. +// Balance final = Causado − Acreditable. +const bucketCausadoPos = (ctx: { esEmisor: string }) => `( + ${ctx.esEmisor} AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE') + OR tipo_comprobante = 'P' + ) +)`; +const bucketCausadoNeg = (ctx: { esEmisor: string }) => `( + ${ctx.esEmisor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' +)`; +// Art. 5 LIVA fracción I: el IVA acreditable requiere que el gasto cumpla los +// requisitos de deducibilidad ISR. Por Art. 27 fracción III LISR, gastos > $2k +// pagados en efectivo NO son deducibles → su IVA tampoco es acreditable. +// Excluimos esas filas del bucket acreditable POS. Para complementos P, +// comparación con monto_pago_mxn (cada P es pago independiente). +const bucketAcreditablePos = (ctx: { esReceptor: string }) => `( + ${ctx.esReceptor} AND ( + (tipo_comprobante = 'I' AND metodo_pago = 'PUE' AND NOT ${NO_DEDUCIBLE_EFECTIVO_I_PUE}) + OR (tipo_comprobante = 'P' AND NOT ${NO_DEDUCIBLE_EFECTIVO_P}) + ) +)`; +// El bucket NEG (NCs) NO se filtra por la regla del efectivo — una NC recibida +// reduce el universo acreditable independiente de cómo se pagó. Si la NC es +// del proveedor cancelando una factura no-acreditable original, el efecto neto +// sigue correcto porque la factura nunca aportó IVA. +const bucketAcreditableNeg = (ctx: { esReceptor: string }) => `( + ${ctx.esReceptor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' +)`; + +const bucketCausadoAny = ( + ctx: { esEmisor: string }, + considerarActivos: boolean, + considerarNCs: boolean, +) => { + const esEmisorE = ctx.esEmisor.replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1'); + return `(${bucketCausadoPos(ctx)} OR ${bucketCausadoNeg(ctx)} OR ( + ${ctx.esEmisor} AND ${IS_I_PPD_07} AND ${HAS_E_REFERENCING_MISMO_MES(esEmisorE, considerarActivos, considerarNCs)} + ))`; +}; +const bucketAcreditableAny = ( + ctx: { esReceptor: string }, + considerarActivos: boolean, + considerarNCs: boolean, +) => { + const esReceptorE = ctx.esReceptor.replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1'); + return `(${bucketAcreditablePos(ctx)} OR ${bucketAcreditableNeg(ctx)} OR ( + ${ctx.esReceptor} AND ${IS_I_PPD_07} AND ${HAS_E_REFERENCING_MISMO_MES(esReceptorE, considerarActivos, considerarNCs)} + ))`; +}; + +// Signed SUM expressions. La compensación I PUE/07 se removió a petición del +// owner — las I PUE/07 ahora aportan IVA completo y la E/07 (si se emite) +// resta normalmente vía bucket NEG (que ya no filtra TipoRelación). El owner +// asume que la E/07 se emitirá; si el contador la omite, el IVA del anticipo +// se sobrecausa (vs el flujo previo, que era robusto a E/07 ausente). +// +// Rama I PPD/07 con E del mismo mes: la I PPD/07 hereda el IVA de la E que +// la cancela. Sin esto, la E resta IVA en su mes pero la I PPD/07 nunca +// aportó nada (PPD espera al P). Mirror de `i07PpdComp` en dashboard.service.ts. +// El subquery se filtra por mismo lado (emisor↔emisor o receptor↔receptor) +// usando el predicado `esEmisor`/`esReceptor` reescrito al alias `e`. +const signedCausadoTras = ( + ctx: { esEmisor: string }, + considerarActivos: boolean, + considerarNCs: boolean, +) => { + const esEmisorE = ctx.esEmisor.replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1'); + return `CASE + WHEN ${bucketCausadoPos(ctx)} THEN ${IVA_TRAS_EXPR} + WHEN ${ctx.esEmisor} AND ${IS_I_PPD_07} THEN ${SUM_E_REFERENCING_TRAS(esEmisorE, considerarActivos, considerarNCs)} + WHEN ${bucketCausadoNeg(ctx)} THEN -(${IVA_TRAS_EXPR}) + ELSE 0 + END`; +}; +const signedCausadoRet = ( + ctx: { esEmisor: string }, + considerarActivos: boolean, + considerarNCs: boolean, +) => { + const esEmisorE = ctx.esEmisor.replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1'); + return `CASE + WHEN ${bucketCausadoPos(ctx)} THEN ${IVA_RET_EXPR} + WHEN ${ctx.esEmisor} AND ${IS_I_PPD_07} THEN ${SUM_E_REFERENCING_RET(esEmisorE, considerarActivos, considerarNCs)} + WHEN ${bucketCausadoNeg(ctx)} THEN -(${IVA_RET_EXPR}) + ELSE 0 + END`; +}; +const signedAcreditableTras = ( + ctx: { esReceptor: string }, + considerarActivos: boolean, + considerarNCs: boolean, +) => { + const esReceptorE = ctx.esReceptor.replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1'); + return `CASE + WHEN ${bucketAcreditablePos(ctx)} THEN ${IVA_TRAS_EXPR} + WHEN ${ctx.esReceptor} AND ${IS_I_PPD_07} THEN ${SUM_E_REFERENCING_TRAS(esReceptorE, considerarActivos, considerarNCs)} + WHEN ${bucketAcreditableNeg(ctx)} THEN -(${IVA_TRAS_EXPR}) + ELSE 0 + END`; +}; +const signedAcreditableRet = ( + ctx: { esReceptor: string }, + considerarActivos: boolean, + considerarNCs: boolean, +) => { + const esReceptorE = ctx.esReceptor.replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1'); + return `CASE + WHEN ${bucketAcreditablePos(ctx)} THEN ${IVA_RET_EXPR} + WHEN ${ctx.esReceptor} AND ${IS_I_PPD_07} THEN ${SUM_E_REFERENCING_RET(esReceptorE, considerarActivos, considerarNCs)} + WHEN ${bucketAcreditableNeg(ctx)} THEN -(${IVA_RET_EXPR}) + ELSE 0 + END`; +}; + +// Regímenes que SIEMPRE restan deducciones para ISR, sin importar PF/PM. +const REGIMENES_RESTA_DEDUCCIONES = ['606', '612']; + +/** + * Determina la fórmula de base gravable para un régimen fiscal dado el tipo + * de persona (PF o PM via rfcLength). + * + * - `ingresos-deducciones`: base = max(0, ingresos − deducciones) + * - `ingresos`: base = max(0, ingresos) (tasa plana en RESICO PF) + * + * Los regímenes 606 (Arrendamiento) y 612 (PF Empresarial) siempre restan. + * El régimen 626 (RESICO) distingue por tipo de persona: PM (RFC 12) resta + * deducciones, PF (RFC 13) usa tasa plana sobre ingresos. Otros regímenes PM + * (601, 603, 607…) no restan aquí — sus deducciones se consideran vía el + * coeficiente de utilidad en el cálculo del ISR causado (Art. 14 LISR). + * + * Single source of truth — usada por `calcularResumenIsr` (KPI del periodo) + * y `getIsrMensual` (tabla histórico). Duplicar esto antes causó que el + * histórico mostrara `base = ingresos` para RESICO PM. + */ +export function determinarFormulaBaseGravable( + clave: string, + rfcLength: number, +): 'ingresos-deducciones' | 'ingresos' { + if (REGIMENES_RESTA_DEDUCCIONES.includes(clave)) return 'ingresos-deducciones'; + if (clave === '626' && rfcLength === 12) return 'ingresos-deducciones'; + return 'ingresos'; +} + +// Régimen 605 (Sueldos y Salarios): el patrón ya retuvo ISR, no genera +// ingreso/deducción para ISR del contribuyente. Se muestra en Dashboard +// como ingreso general, pero se excluye de cálculos de ISR. +const ISR_EXCLUIR_REGIMEN = `AND regimen_fiscal_emisor != '605'`; +const ISR_EXCLUIR_REGIMEN_REC = `AND regimen_fiscal_receptor != '605'`; + +/** + * Lee IVA mensual agregado por mes desde metricas_mensuales. Solo aplica a + * años cerrados (< año actual) y contribuyente seleccionado. Agrega los 3 + * campos canónicos: iva_trasladado_total, iva_acreditable, iva_retenido_cobrado + * (todos post-refactor, alineados con dashboard). Retorna null si no hay + * filas cacheadas (caller debe caer a on-the-fly). + */ +async function readIvaMensualFromCache( + pool: Pool, + año: number, + contribuyenteId: string, +): Promise | null> { + const safe = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + if (!safe) return null; + const { rows } = await pool.query<{ mes: number; t: string; a: string; r: string }>(` + SELECT mes, + COALESCE(SUM(iva_trasladado_total), 0)::numeric(14,2) as t, + COALESCE(SUM(iva_acreditable), 0)::numeric(14,2) as a, + COALESCE(SUM(iva_retenido_cobrado), 0)::numeric(14,2) as r + FROM metricas_mensuales + WHERE contribuyente_id = $1 AND anio = $2 + GROUP BY mes + `, [safe, año]); + if (rows.length === 0) return null; + const map = new Map(); + for (const row of rows) { + map.set(Number(row.mes), { t: Number(row.t), a: Number(row.a), r: Number(row.r) }); + } + return map; +} + +/** + * IVA Mensual desglosado: trasladado, acreditable, retenido, resultado por mes. + * + * Usa la misma fórmula canónica que `getResumenIva` (6 buckets, retención neta): + * Trasladado = causado bruto (Emit+I+PUE + Emit+P + Recib+E+PUE) + * Acreditable = acreditable bruto (Recib+I+PUE + Recib+P + Emit+E+PUE) + * Retenido = retención(causado) − retención(acreditable) + * Resultado = T − A − R + * + * Read-through cache: años pasados con contribuyente seleccionado leen de + * `metricas_mensuales`. El año actual y sin-contribuyente siguen on-the-fly. + */ +export async function getIvaMensual( + pool: Pool, + año: number, + tenantId: string, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise { + // Cache read-through: solo si año pasado, sin conciliación, con contribuyente y flags default + const currentYear = new Date().getFullYear(); + const cacheable = + process.env.METRICAS_BYPASS_CACHE !== '1' && + año < currentYear && + !conciliacion && + considerarActivos && + considerarNCs && + !!contribuyenteId; + + let perMes: Map | null = null; + if (cacheable) { + perMes = await readIvaMensualFromCache(pool, año, contribuyenteId!); + } + + if (!perMes) { + // On-the-fly: dos queries agregadas por mes (causado y acreditable), + // mismos buckets que getResumenIva. Filtro por RFC (ctx.esEmisor/esReceptor) + // en vez de `type` para evitar inconsistencias multi-contribuyente. + const FR = getFR(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const REGIMEN_TENANT = regimenTenantExpr(ctx); + const añoStart = `${año}-01-01`; + const añoEnd = `${año}-12-31`; + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + const [{ rows: causadoRows }, { rows: acreditableRows }] = await Promise.all([ + pool.query<{ mes: number; trasladado: string; retencion: string }>(` + SELECT EXTRACT(MONTH FROM ${FECHA_EFECTIVA})::int as mes, + COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado, + COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion + FROM cfdis + WHERE ${bucketCausadoAny(ctx, considerarActivos, considerarNCs)} + AND ${VIGENTE} AND ${FR}${extra} + AND (${REGIMEN_TENANT}) = ANY($3) + GROUP BY mes + `, [añoStart, añoEnd, TODOS_REGIMENES]), + pool.query<{ mes: number; trasladado: string; retencion: string }>(` + SELECT EXTRACT(MONTH FROM ${FECHA_EFECTIVA})::int as mes, + COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado, + COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion + FROM cfdis + WHERE ${bucketAcreditableAny(ctx, considerarActivos, considerarNCs)} + AND ${VIGENTE} AND ${FR}${extra} + AND (${REGIMEN_TENANT}) = ANY($3) + GROUP BY mes + `, [añoStart, añoEnd, TODOS_REGIMENES]), + ]); + + perMes = new Map(); + for (const row of causadoRows) { + const acc = perMes.get(Number(row.mes)) || { t: 0, a: 0, r: 0 }; + acc.t += Number(row.trasladado); + acc.r += Number(row.retencion); + perMes.set(Number(row.mes), acc); + } + for (const row of acreditableRows) { + const acc = perMes.get(Number(row.mes)) || { t: 0, a: 0, r: 0 }; + acc.a += Number(row.trasladado); + acc.r -= Number(row.retencion); + perMes.set(Number(row.mes), acc); + } + } + + const result: IvaMensual[] = []; + let acumulado = 0; + + for (let m = 1; m <= 12; m++) { + const monthData = perMes.get(m) || { t: 0, a: 0, r: 0 }; + const t = monthData.t; + const a = monthData.a; + const r = monthData.r; + const resultado = t - a - r; + acumulado += resultado; + + result.push({ + id: 0, + año, + mes: m, + ivaTrasladado: t, + ivaAcreditable: a, + ivaRetenido: r, + resultado, + acumulado, + estado: 'pendiente', + fechaDeclaracion: null, + }); + } + + return result; +} + +/** + * ISR Mensual desglosado: ingresos, deducciones, base gravable por mes del año. + */ +export async function getIsrMensual( + pool: Pool, + año: number, + tenantId: string, + conciliacion?: boolean, + contribuyenteId?: string | null, + regimenClave?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise { + // Reutiliza la misma lógica que las cards (calcular{Ingresos,Egresos}PorRegimen) + // aplicada a cada mes del año. Esto garantiza que la tabla "Histórico ISR" cuadre + // célula a célula con los KPIs del periodo activo (reglas por grupo de régimen, + // resta de notas de crédito, pagos P solo cuentan lo cobrado, etc.). + // El RFC del contribuyente determina si régimen 626 resta deducciones (PM) o no (PF). + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const rfcLength = ctx.rfcLength; + const result: IsrMensual[] = []; + + for (let m = 1; m <= 12; m++) { + const lastDay = new Date(año, m, 0).getDate(); + const mm = String(m).padStart(2, '0'); + const dd = String(lastDay).padStart(2, '0'); + const fi = `${año}-${mm}-01`; + const ff = `${año}-${mm}-${dd}`; + + const [ingresosData, egresosData, ncsEmData, ncsRecData] = await Promise.all([ + calcularIngresosPorRegimen(pool, tenantId, fi, ff, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + calcularEgresosPorRegimen(pool, tenantId, fi, ff, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + calcularNcsEmitidasPorRegimen(pool, tenantId, fi, ff, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + calcularNcsRecibidasPorRegimen(pool, tenantId, fi, ff, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + ]); + + let ing: number; + let ded: number; + let ncsEm: number; + let ncsRec: number; + let base: number; + + if (regimenClave) { + ing = ingresosData.porRegimen.find(r => r.regimenClave === regimenClave)?.monto || 0; + ded = egresosData.porRegimen.find(r => r.regimenClave === regimenClave)?.monto || 0; + ncsEm = ncsEmData.porRegimen.find(r => r.regimenClave === regimenClave)?.monto || 0; + ncsRec = ncsRecData.porRegimen.find(r => r.regimenClave === regimenClave)?.monto || 0; + const formula = determinarFormulaBaseGravable(regimenClave, rfcLength); + base = formula === 'ingresos-deducciones' ? Math.max(0, ing - ded) : Math.max(0, ing); + } else { + // Sin régimen: agregar por régimen aplicando la fórmula correcta a cada + // uno y sumar las bases (no aplicar ing − ded global, porque algunos + // regímenes no restan deducciones — ej. RESICO PF, otros PM). + const regimenesConDatos = new Set([ + ...ingresosData.porRegimen.map(r => r.regimenClave).filter(c => c !== '605'), + ...egresosData.porRegimen.map(r => r.regimenClave).filter(c => c !== '605'), + ]); + ing = 0; + ded = 0; + ncsEm = ncsEmData.porRegimen.filter(r => r.regimenClave !== '605').reduce((s, r) => s + r.monto, 0); + ncsRec = ncsRecData.porRegimen.filter(r => r.regimenClave !== '605').reduce((s, r) => s + r.monto, 0); + base = 0; + for (const clave of regimenesConDatos) { + const ingReg = ingresosData.porRegimen.find(r => r.regimenClave === clave)?.monto || 0; + const dedReg = egresosData.porRegimen.find(r => r.regimenClave === clave)?.monto || 0; + const formula = determinarFormulaBaseGravable(clave, rfcLength); + ing += ingReg; + ded += dedReg; + base += formula === 'ingresos-deducciones' + ? Math.max(0, ingReg - dedReg) + : Math.max(0, ingReg); + } + } + + result.push({ + id: 0, + año, + mes: m, + ingresosAcumulados: ing, + deducciones: ded, + baseGravable: base, + ncsEmitidas: ncsEm, + ncsRecibidas: ncsRec, + ncsEmitidasAcum: 0, // se llena en el segundo pase abajo + ncsRecibidasAcum: 0, + ingresosAcum: 0, + deduccionesAcum: 0, + baseGravableAcum: 0, + isrCausado: 0, + isrRetenido: 0, + isrAPagar: 0, + estado: 'pendiente', + fechaDeclaracion: null, + }); + } + + // Running totals: para cada mes, acumular ingresos y deducciones desde enero + // hasta ese mes inclusive. baseGravableAcum NO se clampa — los déficits se + // muestran negativos en la UI y solo se clampan al pasar a ISR causado. + let ingAcum = 0; + let dedAcum = 0; + let ncsEmAcum = 0; + let ncsRecAcum = 0; + for (const row of result) { + ingAcum += row.ingresosAcumulados; // (campo mensual, naming heredado) + dedAcum += row.deducciones; + ncsEmAcum += row.ncsEmitidas; + ncsRecAcum += row.ncsRecibidas; + row.ingresosAcum = ingAcum; + row.deduccionesAcum = dedAcum; + row.ncsEmitidasAcum = ncsEmAcum; + row.ncsRecibidasAcum = ncsRecAcum; + row.baseGravableAcum = ingAcum - dedAcum; + } + + return result; +} + +/** + * Read-through cache para ResumenIva: lee `iva_trasladado_total`, + * `iva_acreditable`, `iva_retenido_cobrado` desde `metricas_mensuales` cuando + * el rango cae en años pasados con contribuyente seleccionado. Calcula el + * acumulado anual on-the-fly (su rango difiere de fechaInicio-fechaFin). + * + * Si no hay filas cacheadas, retorna `null` y el caller cae al path on-the-fly. + */ +async function readResumenIvaFromCache( + pool: Pool, + range: CacheRange, + fechaInicio: string, + fechaFin: string, + conciliacion: boolean | undefined, + ctx: { esEmisor: string; esReceptor: string }, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise { + const { rows } = await pool.query<{ + regimen: string; + trasladado: string; + acreditable: string; + retenido: string; + }>(` + SELECT regimen_fiscal AS regimen, + COALESCE(SUM(iva_trasladado_total), 0)::numeric(14,2) AS trasladado, + COALESCE(SUM(iva_acreditable), 0)::numeric(14,2) AS acreditable, + COALESCE(SUM(iva_retenido_cobrado), 0)::numeric(14,2) AS retenido + FROM metricas_mensuales + WHERE contribuyente_id = $1 + AND make_date(anio, mes, 1) BETWEEN $2::date AND $3::date + AND regimen_fiscal IS NOT NULL + GROUP BY regimen_fiscal + `, [range.contribuyenteId, range.startDate, range.endDate]); + + if (rows.length === 0) return null; + + const catalogo = await prisma.regimen.findMany({ where: { activo: true } }); + const descMap = new Map(catalogo.map(r => [r.clave, r.descripcion])); + + let trasladado = 0; + let acreditable = 0; + let retenido = 0; + const trasladadoPorRegimen: IvaRegimenDetalle[] = []; + const acreditablePorRegimen: IvaRegimenDetalle[] = []; + const retenidoPorRegimen: IvaRegimenDetalle[] = []; + + for (const r of rows) { + const tras = Number(r.trasladado); + const acr = Number(r.acreditable); + const ret = Number(r.retenido); + trasladado += tras; + acreditable += acr; + retenido += ret; + const desc = descMap.get(r.regimen) || r.regimen; + if (tras !== 0) trasladadoPorRegimen.push({ regimenClave: r.regimen, regimenDescripcion: desc, monto: tras }); + if (acr !== 0) acreditablePorRegimen.push({ regimenClave: r.regimen, regimenDescripcion: desc, monto: acr }); + if (ret !== 0) retenidoPorRegimen.push({ regimenClave: r.regimen, regimenDescripcion: desc, monto: ret }); + } + + const resultado = trasladado - acreditable - retenido; + + // Acumulado anual: su rango (year-01-01 → fechaFin) difiere del rango cacheado; + // se calcula on-the-fly contra raw cfdis. Una sola query. + const añoInicio = new Date(fechaInicio + 'T00:00:00').getFullYear(); + const acumFR = conciliacion ? FECHA_RANGO_CONCILIACION : FECHA_RANGO; + const REGIMEN_TENANT = regimenTenantExpr(ctx); + const acumRow = (await pool.query(` + SELECT + COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) - + COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) - + ( + COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) - + COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0) + ) as total + FROM cfdis + WHERE ${VIGENTE} + AND (${REGIMEN_TENANT}) = ANY($3) + AND ${acumFR} + AND (${ctx.esEmisor} OR ${ctx.esReceptor}) + `, [`${añoInicio}-01-01`, fechaFin, TODOS_REGIMENES])).rows[0]; + + // Cache hit retorna 0/empty para los surface IVA No Acreditable. El cache + // aún no persiste esos campos — si se hace crítico para BI, agregar columna + // `iva_no_acreditable_efectivo` a metricas_mensuales y poblarla en + // `metricas-compute.service.ts`. + return { + trasladado, + trasladadoPorRegimen, + acreditable, + acreditablePorRegimen, + retenido, + retenidoPorRegimen, + resultado, + acumuladoAnual: Number(acumRow?.total || 0), + ivaNoAcreditableEfectivo: 0, + ivaNoAcreditableEfectivoPorRegimen: [], + }; +} + +/** + * Resumen IVA para un rango de fechas, desglosado por régimen. + * + * Alineado con la fórmula del dashboard `calcularIvaBalancePorRegimen`: + * Resultado = Trasladado − Acreditable − Retenido + * + * Donde cada tarjeta usa los mismos 6 buckets del dashboard, pero con + * retención **separada** (en el dashboard la retención está embebida en + * el IVA neto de cada bucket; aquí se exhibe en su propia tarjeta para + * Control de Impuestos). + * + * Trasladado = causado bruto (Emit+I+PUE) + (Emit+P) + (Recib+E+PUE) + * Acreditable = acreditable bruto (Recib+I+PUE) + (Recib+P) + (Emit+E+PUE) + * Retenido = retención(causado) − retención(acreditable) + * + * Algebraicamente: T − A − R == dashboard.balance, céntimo por céntimo. + */ +export async function getResumenIva( + pool: Pool, + fechaInicio: string, + fechaFin: string, + tenantId: string, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise { + const FR = getFR(conciliacion); + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const REGIMEN_TENANT = regimenTenantExpr(ctx); + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + // Read-through cache (pasa ctx completo para derivar regimen + signed exprs). + // Solo aplica cuando flags son default (true) para garantizar que las queries + // filtradas no lean datos del caché completo. + const cacheRange = + considerarActivos && considerarNCs + ? planCache(fechaInicio, fechaFin, conciliacion, contribuyenteId) + : null; + if (cacheRange) { + const cached = await readResumenIvaFromCache(pool, cacheRange, fechaInicio, fechaFin, conciliacion, ctx, considerarActivos, considerarNCs); + if (cached) return cached; + } + + // Una query por lado (causado / acreditable). Filtro por RFC via + // ctx.esEmisor/esReceptor (embedded en buckets/signed exprs). + const [{ rows: causadoRows }, { rows: acreditableRows }] = await Promise.all([ + pool.query<{ regimen: string | null; trasladado: string; retencion: string }>(` + SELECT ${REGIMEN_TENANT} as regimen, + COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado, + COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion + FROM cfdis + WHERE ${bucketCausadoAny(ctx, considerarActivos, considerarNCs)} + AND ${VIGENTE} AND ${FR}${extra} + AND (${REGIMEN_TENANT}) = ANY($3) + GROUP BY ${REGIMEN_TENANT} + `, [fechaInicio, fechaFin, TODOS_REGIMENES]), + pool.query<{ regimen: string | null; trasladado: string; retencion: string }>(` + SELECT ${REGIMEN_TENANT} as regimen, + COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado, + COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion + FROM cfdis + WHERE ${bucketAcreditableAny(ctx, considerarActivos, considerarNCs)} + AND ${VIGENTE} AND ${FR}${extra} + AND (${REGIMEN_TENANT}) = ANY($3) + GROUP BY ${REGIMEN_TENANT} + `, [fechaInicio, fechaFin, TODOS_REGIMENES]), + ]); + + // Combinar por régimen: el set de régimenes posibles es la unión de ambos lados. + type Acc = { trasCausado: number; retCausado: number; trasAcreditable: number; retAcreditable: number }; + const porRegimen = new Map(); + const ensure = (k: string): Acc => { + let v = porRegimen.get(k); + if (!v) { v = { trasCausado: 0, retCausado: 0, trasAcreditable: 0, retAcreditable: 0 }; porRegimen.set(k, v); } + return v; + }; + for (const r of causadoRows) { + if (!r.regimen) continue; + const acc = ensure(r.regimen); + acc.trasCausado += Number(r.trasladado); + acc.retCausado += Number(r.retencion); + } + for (const r of acreditableRows) { + if (!r.regimen) continue; + const acc = ensure(r.regimen); + acc.trasAcreditable += Number(r.trasladado); + acc.retAcreditable += Number(r.retencion); + } + + const catalogo = await prisma.regimen.findMany({ where: { activo: true } }); + const descMap = new Map(catalogo.map(r => [r.clave, r.descripcion])); + + let trasladado = 0; + let acreditable = 0; + let retenido = 0; + const trasladadoPorRegimen: IvaRegimenDetalle[] = []; + const acreditablePorRegimen: IvaRegimenDetalle[] = []; + const retenidoPorRegimen: IvaRegimenDetalle[] = []; + + for (const [regimen, acc] of porRegimen) { + const tras = acc.trasCausado; + const acre = acc.trasAcreditable; + const ret = acc.retCausado - acc.retAcreditable; + trasladado += tras; + acreditable += acre; + retenido += ret; + const desc = descMap.get(regimen) || regimen; + if (tras !== 0) trasladadoPorRegimen.push({ regimenClave: regimen, regimenDescripcion: desc, monto: tras }); + if (acre !== 0) acreditablePorRegimen.push({ regimenClave: regimen, regimenDescripcion: desc, monto: acre }); + if (ret !== 0) retenidoPorRegimen.push({ regimenClave: regimen, regimenDescripcion: desc, monto: ret }); + } + + const resultado = trasladado - acreditable - retenido; + + // Acumulado anual (misma fórmula T − A − R, pero rango = enero → fechaFin). + const añoInicio = new Date(fechaInicio + 'T00:00:00').getFullYear(); + const acumFR = conciliacion ? FECHA_RANGO_CONCILIACION : FECHA_RANGO; + const { rows: [acumRow] } = await pool.query(` + SELECT + COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) - + COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) - + ( + COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) - + COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0) + ) as total + FROM cfdis + WHERE ${VIGENTE} + AND (${REGIMEN_TENANT}) = ANY($3) + AND ${acumFR}${extra} + AND (${ctx.esEmisor} OR ${ctx.esReceptor}) + `, [`${añoInicio}-01-01`, fechaFin, TODOS_REGIMENES]); + + // IVA No Acreditable surface (Art. 5 LIVA fracción I + Art. 27 fracción III LISR). + // No participa en `resultado` — ya excluido del `acreditable` arriba via filtro + // en `bucketAcreditablePos`. Aquí solo se exhibe el monto excluido para el contador. + const noAcreditableData = await calcularIvaNoAcreditableEfectivoPorRegimen( + pool, tenantId, fechaInicio, fechaFin, undefined, undefined, + conciliacion, contribuyenteId, considerarActivos, considerarNCs, + ); + + return { + trasladado, + trasladadoPorRegimen, + acreditable, + acreditablePorRegimen, + retenido, + retenidoPorRegimen, + resultado, + acumuladoAnual: Number(acumRow?.total || 0), + ivaNoAcreditableEfectivo: noAcreditableData.total, + ivaNoAcreditableEfectivoPorRegimen: noAcreditableData.porRegimen, + }; +} + +/** + * Calcula ISR progresivo según tarifa del Art. 96 + */ +async function calcularIsrProgresivo(baseGravable: number, anio: number): Promise { + if (baseGravable <= 0) return 0; + + const tarifas = await prisma.isrTarifa.findMany({ + where: { anio }, + orderBy: { limiteInferior: 'asc' }, + }); + + if (tarifas.length === 0) return 0; + + // Encontrar el rango correcto + let tarifa = tarifas[tarifas.length - 1]; // default: último rango + for (const t of tarifas) { + const ls = t.limiteSuperior ? Number(t.limiteSuperior) : Infinity; + if (baseGravable >= Number(t.limiteInferior) && baseGravable <= ls) { + tarifa = t; + break; + } + } + + const excedente = baseGravable - Number(tarifa.limiteInferior); + const impuestoMarginal = excedente * (Number(tarifa.porcentajeExcedente) / 100); + return Number(tarifa.cuotaFija) + impuestoMarginal; +} + +/** + * Calcula ISR RESICO PF según Art. 113-E + */ +async function calcularIsrResicoPF(ingresos: number, anio: number): Promise { + if (ingresos <= 0) return 0; + + const tasas = await prisma.isrResicoTasa.findMany({ + where: { anio }, + orderBy: { montoMaximo: 'asc' }, + }); + + if (tasas.length === 0) return 0; + + for (const t of tasas) { + if (ingresos <= Number(t.montoMaximo)) { + return ingresos * (Number(t.porcentaje) / 100); + } + } + + // Si supera todos los rangos, usar el último + const ultima = tasas[tasas.length - 1]; + return ingresos * (Number(ultima.porcentaje) / 100); +} + +/** + * Resumen ISR con cálculo por régimen, coeficiente de utilidad, ISR progresivo/RESICO. + */ +export async function getResumenIsr( + pool: Pool, + fechaInicio: string, + fechaFin: string, + tenantId: string, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise { + const FR = getFR(conciliacion); + const extra = buildExtraFilters(considerarActivos, considerarNCs); + + // Ingresos + Deducciones + NCs (emitidas/recibidas) + Gastos no deducibles + // (efectivo > $2k) en paralelo. Las NCs se necesitan en el cálculo de base + // gravable para regímenes con fórmula `ingresos-deducciones` (ver loop abajo). + const [ingresosData, egresosData, ncsEmitidasData, ncsRecibidasData, gastosNoDedData] = await Promise.all([ + calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + calcularEgresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + calcularNcsEmitidasPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + calcularNcsRecibidasPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + calcularGastosNoDeduciblesEfectivoPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + ]); + + // RFC del contribuyente (o tenant) para determinar persona moral/física + const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId); + const rfcLength = ctx.rfcLength; + + // Base gravable por régimen + const baseGravablePorRegimen: import('@horux/shared').BaseGravableRegimen[] = []; + + // Todos los regímenes que tienen ingresos/egresos/NCs (excluir 605 — sueldos, + // ISR retenido por patrón) + const regimenesConDatos = new Set([ + ...ingresosData.porRegimen.map(r => r.regimenClave).filter(c => c !== '605'), + ...egresosData.porRegimen.map(r => r.regimenClave).filter(c => c !== '605'), + ...ncsEmitidasData.porRegimen.map(r => r.regimenClave).filter(c => c !== '605'), + ...ncsRecibidasData.porRegimen.map(r => r.regimenClave).filter(c => c !== '605'), + ]); + + const catalogo = await prisma.regimen.findMany({ where: { activo: true } }); + const descMap = new Map(catalogo.map(r => [r.clave, r.descripcion])); + + for (const clave of regimenesConDatos) { + const ing = ingresosData.porRegimen.find(r => r.regimenClave === clave)?.monto || 0; + const ded = egresosData.porRegimen.find(r => r.regimenClave === clave)?.monto || 0; + const ncsEm = ncsEmitidasData.porRegimen.find(r => r.regimenClave === clave)?.monto || 0; + const ncsRec = ncsRecibidasData.porRegimen.find(r => r.regimenClave === clave)?.monto || 0; + + const formula = determinarFormulaBaseGravable(clave, rfcLength); + // Para `ingresos-deducciones` (606, 612, 626 RESICO PM): la base gravable + // ajusta por NCs de ambos lados: + // ingresoNeto = ingresosNominales − ncsEmitidas + // deduccionNeta = deducciones − ncsRecibidas + // base = max(0, ingresoNeto − deduccionNeta) + // = max(0, ingNominales − ncsEm − ded + ncsRec) + // Para `ingresos` (RESICO PF, RIF, Plataformas, PMs Grupo 3): no se aplica + // ajuste de NCs — esos regímenes no restan deducciones aquí (ver + // determinarFormulaBaseGravable). + const baseGravable = formula === 'ingresos-deducciones' + ? Math.max(0, ing - ncsEm - ded + ncsRec) + : Math.max(0, ing); + + if (baseGravable !== 0 || ing !== 0) { + baseGravablePorRegimen.push({ + regimenClave: clave, + regimenDescripcion: descMap.get(clave) || clave, + ingresos: ing, + deducciones: formula === 'ingresos-deducciones' ? ded : 0, + baseGravable, + isrCausado: 0, // calculated below + formula, + }); + } + } + + // Exclude 605 from ISR totals (sueldos — ISR already withheld by employer) + const ingresosPorRegimen = ingresosData.porRegimen + .filter(r => r.regimenClave !== '605') + .map(r => ({ regimenClave: r.regimenClave, regimenDescripcion: r.regimenDescripcion, monto: r.monto })); + const ingresosAcumulados = ingresosPorRegimen.reduce((s, r) => s + r.monto, 0); + const deduccionesPorRegimen = egresosData.porRegimen + .filter(r => r.regimenClave !== '605') + .map(r => ({ regimenClave: r.regimenClave, regimenDescripcion: r.regimenDescripcion, monto: r.monto })); + const deducciones = deduccionesPorRegimen.reduce((s, r) => s + r.monto, 0); + const baseGravableTotal = baseGravablePorRegimen.reduce((s, r) => s + r.baseGravable, 0); + + // ISR Retenido — filtro por RFC del contribuyente (cualquier lado). + const { rows: [retRow] } = await pool.query(` + SELECT COALESCE(SUM(COALESCE(isr_retencion_mxn,0) - (${EXCL_ISR_RET})), 0) as total + FROM cfdis + WHERE ${VIGENTE} AND ${FR}${extra} + AND (${ctx.esEmisor} OR ${ctx.esReceptor}) + `, [fechaInicio, fechaFin]); + + // ISR Causado por régimen + const anio = new Date(fechaFin + 'T00:00:00').getFullYear(); + + // Coeficiente de utilidad del tenant (para PM y otros) + const coefData = await prisma.coeficienteUtilidad.findUnique({ + where: { tenantId_anio: { tenantId, anio } }, + }); + const coeficiente = coefData ? Number(coefData.coeficiente) : 0; + + let isrCausado = 0; + + for (const reg of baseGravablePorRegimen) { + let regIsrCausado = 0; + if (reg.regimenClave === '626' && rfcLength === 13) { + // RESICO PF: tasa plana por bracket (Art. 113-E LISR) + regIsrCausado = await calcularIsrResicoPF(reg.baseGravable, anio); + } else if (reg.regimenClave === '626' && rfcLength === 12) { + // 626 RESICO PM: tasa fija 30% directa sobre la base gravable. + // Comparte la fórmula de base gravable con PF Empresarial + // (`ingresos − ncsEm − ded + ncsRec`) pero NO usa Art. 96 ni + // coeficiente de utilidad — aplicación directa del 30% por decisión + // del cliente (2026-05-02). + regIsrCausado = reg.baseGravable * 0.30; + } else if (['606', '612', '621', '625'].includes(reg.regimenClave)) { + // PF Empresarial: tarifa progresiva Art. 96 + regIsrCausado = await calcularIsrProgresivo(reg.baseGravable, anio); + } else { + // PM Grupo 3: base × coeficiente × tasa (30%) + const basePM = reg.baseGravable * (coeficiente || 0.30); + regIsrCausado = basePM * 0.30; + } + reg.isrCausado = Math.round(regIsrCausado * 100) / 100; + isrCausado += regIsrCausado; + } + + const isrRetenido = Number(retRow?.total || 0); + const isrAPagar = Math.max(0, isrCausado - isrRetenido); + + // ncsEmitidasData + ncsRecibidasData se calcularon up-front en el Promise.all + // de arriba (necesarias para la fórmula de base gravable). Aquí solo se + // exponen en la respuesta para los KPIs surface-only "NCs Emitidas" / + // "NCs Recibidas". + + return { + ingresosAcumulados, + ingresosPorRegimen, + deducciones, + deduccionesPorRegimen, + baseGravable: baseGravableTotal, + baseGravablePorRegimen, + isrCausado: Math.round(isrCausado * 100) / 100, + isrRetenido, + isrAPagar: Math.round(isrAPagar * 100) / 100, + ncsEmitidas: ncsEmitidasData.total, + ncsEmitidasPorRegimen: ncsEmitidasData.porRegimen, + ncsRecibidas: ncsRecibidasData.total, + ncsRecibidasPorRegimen: ncsRecibidasData.porRegimen, + gastosNoDeduciblesEfectivo: gastosNoDedData.total, + gastosNoDeduciblesEfectivoPorRegimen: gastosNoDedData.porRegimen, + }; +} + +/** + * Desglose del cálculo provisional ISR para el mes final del filtro. + * + * Tres llamadas a getResumenIsr con rangos distintos: + * - delPeriodo: solo el mes final del filtro (1 mes calendario) + * - anteriores: enero hasta el mes anterior al final (vacío si mesFinal=1) + * - total: enero hasta el mes final inclusive + * + * Si mesFinal === 1, la rama "anteriores" no llama al backend — retorna ceros + * para evitar un query inútil. + */ +export async function getResumenIsrDesglosado( + pool: Pool, + fechaFin: string, + tenantId: string, + conciliacion?: boolean, + contribuyenteId?: string | null, + considerarActivos: boolean = true, + considerarNCs: boolean = true, +): Promise { + const fechaFinDate = new Date(fechaFin + 'T00:00:00'); + const anio = fechaFinDate.getFullYear(); + const mesFinal = fechaFinDate.getMonth() + 1; // 1-12 + + // Helper para construir rango fin de mes + const mmFinal = String(mesFinal).padStart(2, '0'); + const ultDiaFinal = new Date(anio, mesFinal, 0).getDate(); + const ultDiaFinalStr = String(ultDiaFinal).padStart(2, '0'); + + // delPeriodo: 1er a último día del mes final + const fiPeriodo = `${anio}-${mmFinal}-01`; + const ffPeriodo = `${anio}-${mmFinal}-${ultDiaFinalStr}`; + + // anteriores: enero 1 al último día del (mesFinal - 1). Vacío si mesFinal=1. + let anteriores: import('@horux/shared').ResumenIsr; + if (mesFinal === 1) { + anteriores = emptyResumenIsr(); + } else { + const mesAntes = mesFinal - 1; + const mmAntes = String(mesAntes).padStart(2, '0'); + const ultDiaAntes = new Date(anio, mesAntes, 0).getDate(); + const ultDiaAntesStr = String(ultDiaAntes).padStart(2, '0'); + const fiAnt = `${anio}-01-01`; + const ffAnt = `${anio}-${mmAntes}-${ultDiaAntesStr}`; + anteriores = await getResumenIsr(pool, fiAnt, ffAnt, tenantId, conciliacion, contribuyenteId, considerarActivos, considerarNCs); + } + + // total: enero 1 al último día del mes final + const fiTotal = `${anio}-01-01`; + const ffTotal = `${anio}-${mmFinal}-${ultDiaFinalStr}`; + + const [delPeriodo, total] = await Promise.all([ + getResumenIsr(pool, fiPeriodo, ffPeriodo, tenantId, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + getResumenIsr(pool, fiTotal, ffTotal, tenantId, conciliacion, contribuyenteId, considerarActivos, considerarNCs), + ]); + + return { delPeriodo, anteriores, total, mesFinal, anio }; +} + +function emptyResumenIsr(): import('@horux/shared').ResumenIsr { + return { + ingresosAcumulados: 0, + ingresosPorRegimen: [], + deducciones: 0, + deduccionesPorRegimen: [], + baseGravable: 0, + baseGravablePorRegimen: [], + isrCausado: 0, + isrRetenido: 0, + isrAPagar: 0, + ncsEmitidas: 0, + ncsEmitidasPorRegimen: [], + ncsRecibidas: 0, + ncsRecibidasPorRegimen: [], + gastosNoDeduciblesEfectivo: 0, + gastosNoDeduciblesEfectivoPorRegimen: [], + }; +} + +/** + * Obtener coeficiente de utilidad del tenant para un año. + */ +export async function getCoeficiente(tenantId: string, anio: number) { + const data = await prisma.coeficienteUtilidad.findUnique({ + where: { tenantId_anio: { tenantId, anio } }, + }); + return { anio, coeficiente: data ? Number(data.coeficiente) : null }; +} + +/** + * Establecer coeficiente de utilidad del tenant para un año. + */ +export async function setCoeficiente(tenantId: string, anio: number, coeficiente: number) { + const data = await prisma.coeficienteUtilidad.upsert({ + where: { tenantId_anio: { tenantId, anio } }, + update: { coeficiente }, + create: { tenantId, anio, coeficiente }, + }); + return { anio: data.anio, coeficiente: Number(data.coeficiente) }; +} diff --git a/apps/api/src/services/metabase.service.ts b/apps/api/src/services/metabase.service.ts new file mode 100644 index 0000000..86ab04e --- /dev/null +++ b/apps/api/src/services/metabase.service.ts @@ -0,0 +1,179 @@ +/** + * Metabase integration service. + * Automatically registers newly-provisioned tenant databases in Metabase + * (al crear tenant) y las elimina (al desactivar tenant). Auth via session + * token con cache de 13 días (Metabase los expira a 14). + * + * Variables de entorno (todas opcionales — si METABASE_PASSWORD o + * METABASE_PG_PASSWORD faltan, las llamadas se logean y skipean sin romper): + * METABASE_URL (default http://192.168.10.170:3000) + * METABASE_USERNAME (default ialcarazsalazar@consultoria-as.com) + * METABASE_PASSWORD password de la cuenta admin Metabase + * METABASE_PG_HOST (default 192.168.10.90) + * METABASE_PG_PORT (default 5432) + * METABASE_PG_USER (default postgres) + * METABASE_PG_PASSWORD password Postgres que Metabase usa para conectar + */ + +const METABASE_URL = process.env.METABASE_URL || 'http://192.168.10.170:3000'; +const METABASE_USERNAME = process.env.METABASE_USERNAME || 'ialcarazsalazar@consultoria-as.com'; +const METABASE_PASSWORD = process.env.METABASE_PASSWORD || ''; + +// PostgreSQL connection details exposed to Metabase +const PG_HOST = process.env.METABASE_PG_HOST || '192.168.10.90'; +const PG_PORT = parseInt(process.env.METABASE_PG_PORT || '5432', 10); +const PG_USER = process.env.METABASE_PG_USER || 'postgres'; +const PG_PASSWORD = process.env.METABASE_PG_PASSWORD || ''; + +let cachedSessionToken: string | null = null; +let tokenExpiresAt = 0; + +async function getSessionToken(): Promise { + // Re-use cached token if still valid (Metabase sessions last 2 weeks by default) + if (cachedSessionToken && Date.now() < tokenExpiresAt) { + return cachedSessionToken; + } + + if (!METABASE_PASSWORD) { + console.error('[METABASE] METABASE_PASSWORD not configured'); + return null; + } + + try { + const res = await fetch(`${METABASE_URL}/api/session`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: METABASE_USERNAME, + password: METABASE_PASSWORD, + }), + }); + + if (!res.ok) { + const text = await res.text(); + console.error(`[METABASE] Auth failed: ${res.status} ${text}`); + return null; + } + + const data = await res.json() as { id?: string }; + if (!data.id) { + console.error('[METABASE] Auth response missing session id'); + return null; + } + + cachedSessionToken = data.id; + tokenExpiresAt = Date.now() + 13 * 24 * 60 * 60 * 1000; // 13 days + return cachedSessionToken; + } catch (err) { + console.error('[METABASE] Error fetching session token:', err); + return null; + } +} + +interface RegisterDatabaseInput { + nombre: string; + dbName: string; +} + +export async function registerDatabase(input: RegisterDatabaseInput): Promise { + const sessionToken = await getSessionToken(); + if (!sessionToken) { + console.error('[METABASE] Skipping database registration — no session token'); + return; + } + + if (!PG_PASSWORD) { + console.error('[METABASE] METABASE_PG_PASSWORD not configured'); + return; + } + + const payload = { + name: input.nombre, + engine: 'postgres', + details: { + host: PG_HOST, + port: PG_PORT, + dbname: input.dbName, + user: PG_USER, + password: PG_PASSWORD, + ssl: false, + 'tunnel-enabled': false, + 'advanced-options': false, + 'schema-filters-type': 'all', + }, + auto_run_queries: true, + is_full_sync: true, + is_on_demand: false, + }; + + try { + const res = await fetch(`${METABASE_URL}/api/database`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Metabase-Session': sessionToken, + }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const text = await res.text(); + // 409 or duplicate name is not fatal — log and continue + if (res.status === 400 && text.includes('already exists')) { + console.log(`[METABASE] Database "${input.nombre}" already registered`); + return; + } + console.error(`[METABASE] Register database failed: ${res.status} ${text}`); + return; + } + + const data = await res.json() as { id?: number }; + console.log(`[METABASE] Database "${input.nombre}" registered with id=${data.id}`); + } catch (err) { + console.error('[METABASE] Error registering database:', err); + } +} + +export async function deleteDatabase(databaseName: string): Promise { + const sessionToken = await getSessionToken(); + if (!sessionToken) { + console.error('[METABASE] Skipping database deletion — no session token'); + return; + } + + try { + // Find database by name + const listRes = await fetch(`${METABASE_URL}/api/database`, { + headers: { 'X-Metabase-Session': sessionToken }, + }); + + if (!listRes.ok) { + console.error(`[METABASE] Failed to list databases: ${listRes.status}`); + return; + } + + const listData = await listRes.json() as { data?: Array<{ id: number; name: string; details?: { dbname?: string } }> }; + const db = listData.data?.find( + (d) => d.details?.dbname === databaseName || d.name.includes(databaseName) + ); + + if (!db) { + console.log(`[METABASE] No database found for ${databaseName}`); + return; + } + + const deleteRes = await fetch(`${METABASE_URL}/api/database/${db.id}`, { + method: 'DELETE', + headers: { 'X-Metabase-Session': sessionToken }, + }); + + if (!deleteRes.ok) { + console.error(`[METABASE] Delete database failed: ${deleteRes.status}`); + return; + } + + console.log(`[METABASE] Database ${db.id} (${databaseName}) deleted`); + } catch (err) { + console.error('[METABASE] Error deleting database:', err); + } +} diff --git a/apps/api/src/services/metricas-compute.service.ts b/apps/api/src/services/metricas-compute.service.ts new file mode 100644 index 0000000..455d82d --- /dev/null +++ b/apps/api/src/services/metricas-compute.service.ts @@ -0,0 +1,342 @@ +import type { Pool } from 'pg'; +import { prisma, tenantDb } from '../config/database.js'; +import { + calcularIngresosPorRegimen, + calcularEgresosPorRegimen, + calcularNcsEmitidasPorRegimen, + calcularNcsRecibidasPorRegimen, + calcularGastosNoDeduciblesEfectivoPorRegimen, +} from './dashboard.service.js'; +import { getResumenIva } from './impuestos.service.js'; +import { + upsertMetricaMensual, + getPendingInvalidations, + clearInvalidation, +} from './metricas.service.js'; + +/** + * Tanda A — Cimientos del sistema hot/cold de métricas pre-calculadas. + * + * Este módulo calcula las métricas mensuales agregando desde `cfdis` raw, y las + * guarda en la tabla `metricas_mensuales` del tenant para que los consumers las + * lean sin recomputar. Los consumers aún NO leen de la tabla (Tanda B), esto + * solo llena y mantiene la tabla. + * + * Alcance Tanda A (campos poblados): + * - ingresos_cobrados, egresos_pagados (flujo efectivo, respeta grupo de régimen) + * - iva_trasladado_total, iva_acreditable, iva_retenido_cobrado, iva_resultado + * - utilidad_realizada, flujo_entradas/salidas/neto + * - cfdis_emitidos_count, cfdis_recibidos_count, cfdis_cancelados_count + * + * Fuera de alcance Tanda A (quedan en 0 — iteraciones futuras): + * - Desglose IVA por tasa (16/8/0/exento) + * - ISR causado/retenido/a_pagar (requiere tablas progresivas + coeficiente) + * - IEPS trasladado/acreditable + * - CxC/CxP saldo final + counts + * - ingresos_devengados, egresos_devengados, utilidad_devengada (split PF vs PM) + */ + +// ─────────────────────────────────────────────────────────────── +// Compute para UN (contribuyente, anio, mes) +// ─────────────────────────────────────────────────────────────── + +/** + * Computa y hace upsert de métricas mensuales para un contribuyente en un mes. + * Crea una fila por cada régimen detectado en los CFDIs del mes. Si no hay + * CFDIs en el mes, no inserta nada (filas ausentes = mes sin actividad). + */ +export async function computeMetricaMensual( + pool: Pool, + tenantId: string, + contribuyenteId: string, + anio: number, + mes: number, +): Promise<{ filasEscritas: number }> { + const safeContrib = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + const fi = `${anio}-${String(mes).padStart(2, '0')}-01`; + const lastDay = new Date(anio, mes, 0).getDate(); + const ff = `${anio}-${String(mes).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; + + // DELETE del cache del periodo ANTES de llamar a calcular{Ingresos,Egresos}. + // Crítico: esas funciones hacen read-through cache, así que si encuentran + // filas en metricas_mensuales leen valores viejos y el recompute propaga + // datos stale. Al borrar primero, el read-through no encuentra nada y cae + // al path on-the-fly (que es lo que queremos al recomputar). + await pool.query( + `DELETE FROM metricas_mensuales WHERE contribuyente_id = $1 AND anio = $2 AND mes = $3`, + [safeContrib, anio, mes], + ); + + // Reusa la lógica canónica de los servicios existentes. Paso `_ignorados=[]` + // para que NO filtre régimenes ignorados por el tenant — en la tabla + // almacenamos todos los datos; el consumer decide si filtrar ignorados. + const [ingresos, egresos, resumenIva, ncsEmitidas, ncsRecibidas, noDeducibles] = await Promise.all([ + calcularIngresosPorRegimen(pool, tenantId, fi, ff, [], undefined, false, contribuyenteId), + calcularEgresosPorRegimen(pool, tenantId, fi, ff, [], undefined, false, contribuyenteId), + getResumenIva(pool, fi, ff, tenantId, false, contribuyenteId), + calcularNcsEmitidasPorRegimen(pool, tenantId, fi, ff, [], undefined, false, contribuyenteId), + calcularNcsRecibidasPorRegimen(pool, tenantId, fi, ff, [], undefined, false, contribuyenteId), + calcularGastosNoDeduciblesEfectivoPorRegimen(pool, tenantId, fi, ff, [], undefined, false, contribuyenteId), + ]); + + // Counts de CFDIs del mes (por régimen, usando FECHA_EFECTIVA del fix P) + const { rows: countsRows } = await pool.query<{ + regimen: string | null; + direction: 'E' | 'R'; + vigentes: string; + cancelados: string; + }>(` + SELECT + CASE WHEN type='EMITIDO' THEN regimen_fiscal_emisor ELSE regimen_fiscal_receptor END AS regimen, + CASE WHEN type='EMITIDO' THEN 'E' ELSE 'R' END AS direction, + COUNT(*) FILTER (WHERE status = 'Vigente') AS vigentes, + COUNT(*) FILTER (WHERE status IN ('Cancelado','0')) AS cancelados + FROM cfdis + WHERE EXTRACT(YEAR FROM (CASE WHEN tipo_comprobante='P' THEN fecha_pago_p ELSE fecha_emision END)) = $1 + AND EXTRACT(MONTH FROM (CASE WHEN tipo_comprobante='P' THEN fecha_pago_p ELSE fecha_emision END)) = $2 + AND contribuyente_id = $3 + GROUP BY 1, 2 + `, [anio, mes, safeContrib]); + + // Indexa counts por régimen para lookup + const emitidosPorReg = new Map(); + const recibidosPorReg = new Map(); + for (const r of countsRows) { + const target = r.direction === 'E' ? emitidosPorReg : recibidosPorReg; + target.set(r.regimen, { + vigentes: Number(r.vigentes) || 0, + cancelados: Number(r.cancelados) || 0, + }); + } + + // Régimenes a procesar = unión de los que aparecen en ingresos, egresos, + // NCs emitidas/recibidas o counts. + const regimenes = new Set(); + ingresos.porRegimen.forEach(r => regimenes.add(r.regimenClave)); + egresos.porRegimen.forEach(r => regimenes.add(r.regimenClave)); + ncsEmitidas.porRegimen.forEach(r => regimenes.add(r.regimenClave)); + ncsRecibidas.porRegimen.forEach(r => regimenes.add(r.regimenClave)); + noDeducibles.porRegimen.forEach(r => regimenes.add(r.regimenClave)); + emitidosPorReg.forEach((_, k) => { if (k) regimenes.add(k); }); + recibidosPorReg.forEach((_, k) => { if (k) regimenes.add(k); }); + + // (DELETE ya se hizo al inicio, ver comentario arriba.) + if (regimenes.size === 0) { + return { filasEscritas: 0 }; + } + + let filasEscritas = 0; + for (const regimen of regimenes) { + const ing = ingresos.porRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + const egr = egresos.porRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + const ivaTras = resumenIva.trasladadoPorRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + const ivaAcr = resumenIva.acreditablePorRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + const ivaRet = resumenIva.retenidoPorRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + const ivaResultado = ivaTras - ivaAcr - ivaRet; + const emitidos = emitidosPorReg.get(regimen) || { vigentes: 0, cancelados: 0 }; + const recibidos = recibidosPorReg.get(regimen) || { vigentes: 0, cancelados: 0 }; + const ncsEm = ncsEmitidas.porRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + const ncsRec = ncsRecibidas.porRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + const noDed = noDeducibles.porRegimen.find(r => r.regimenClave === regimen)?.monto || 0; + + await upsertMetricaMensual(pool, contribuyenteId, anio, mes, regimen, { + ivaTrasladadoTotal: ivaTras, + ivaAcreditable: ivaAcr, + ivaRetenidoCobrado: ivaRet, + ivaResultado, + ingresosCobrados: ing, + egresosPagados: egr, + utilidadRealizada: ing - egr, + flujoEntradas: ing, + flujoSalidas: egr, + flujoNeto: ing - egr, + ncsEmitidas: ncsEm, + ncsRecibidas: ncsRec, + gastosNoDeduciblesEfectivo: noDed, + cfdisEmitidosCount: emitidos.vigentes, + cfdisRecibidosCount: recibidos.vigentes, + cfdisCanceladosCount: emitidos.cancelados + recibidos.cancelados, + }); + filasEscritas++; + } + + return { filasEscritas }; +} + +// ─────────────────────────────────────────────────────────────── +// Backfill completo para un tenant +// ─────────────────────────────────────────────────────────────── + +export interface BackfillOptions { + /** Si es true, solo hace dry-run (log), no escribe. */ + dryRun?: boolean; + /** Año desde el cual backfillear. Default: año del CFDI más antiguo. */ + desdeAnio?: number; + /** Año hasta el cual (inclusive). Default: año actual - 1 (el año actual se calcula on-the-fly). */ + hastaAnio?: number; +} + +export interface BackfillResult { + tenantId: string; + contribuyentesProcesados: number; + mesesProcesados: number; + filasEscritas: number; + errores: Array<{ contribuyenteId: string; anio: number; mes: number; error: string }>; +} + +/** + * Itera contribuyentes × años × meses y llena `metricas_mensuales`. Diseñado + * para correrse una vez (bootstrap) o ad-hoc cuando se detecten huecos. + */ +export async function backfillTenant( + tenantId: string, + opts: BackfillOptions = {}, +): Promise { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { databaseName: true, rfc: true }, + }); + if (!tenant) throw new Error(`Tenant ${tenantId} no encontrado`); + + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + + const { rows: contribs } = await pool.query<{ entidad_id: string }>( + `SELECT entidad_id FROM contribuyentes`, + ); + if (contribs.length === 0) { + return { + tenantId, + contribuyentesProcesados: 0, + mesesProcesados: 0, + filasEscritas: 0, + errores: [], + }; + } + + const currentYear = new Date().getFullYear(); + const hastaAnio = opts.hastaAnio ?? currentYear - 1; + + // Para cada contribuyente, determinar rango de años desde el CFDI más antiguo + const result: BackfillResult = { + tenantId, + contribuyentesProcesados: 0, + mesesProcesados: 0, + filasEscritas: 0, + errores: [], + }; + + for (const c of contribs) { + const { rows: [rango] } = await pool.query<{ min_anio: number | null }>( + `SELECT EXTRACT(YEAR FROM MIN(fecha_emision))::int AS min_anio + FROM cfdis WHERE contribuyente_id = $1`, + [c.entidad_id], + ); + if (!rango?.min_anio) continue; // sin CFDIs, skip + + const desdeAnio = opts.desdeAnio ?? rango.min_anio; + result.contribuyentesProcesados++; + + for (let anio = desdeAnio; anio <= hastaAnio; anio++) { + for (let mes = 1; mes <= 12; mes++) { + result.mesesProcesados++; + if (opts.dryRun) continue; + try { + const { filasEscritas } = await computeMetricaMensual(pool, tenantId, c.entidad_id, anio, mes); + result.filasEscritas += filasEscritas; + } catch (err: any) { + result.errores.push({ + contribuyenteId: c.entidad_id, + anio, + mes, + error: err?.message || String(err), + }); + } + } + } + } + + return result; +} + +// ─────────────────────────────────────────────────────────────── +// Procesamiento de invalidaciones (cron) +// ─────────────────────────────────────────────────────────────── + +export interface ProcessResult { + procesadas: number; + filasEscritas: number; + errores: number; +} + +/** + * Lee `metricas_invalidaciones`, recomputa cada (contribuyente, anio, mes) + * marcado, y limpia la invalidación al terminar. Fail-safe: si una entrada + * falla, loguea y continúa con la siguiente. + */ +export async function processInvalidations(pool: Pool, tenantId: string): Promise { + const pending = await getPendingInvalidations(pool); + if (pending.length === 0) return { procesadas: 0, filasEscritas: 0, errores: 0 }; + + let procesadas = 0; + let filasEscritas = 0; + let errores = 0; + + for (const inv of pending) { + try { + const { filasEscritas: fe } = await computeMetricaMensual( + pool, tenantId, inv.contribuyenteId, inv.anio, inv.mes, + ); + filasEscritas += fe; + await clearInvalidation(pool, inv.contribuyenteId, inv.anio, inv.mes); + procesadas++; + } catch (err: any) { + console.error( + `[Metricas] Error computando (tenant=${tenantId}, contrib=${inv.contribuyenteId}, ${inv.anio}-${String(inv.mes).padStart(2, '0')}):`, + err?.message || err, + ); + errores++; + } + } + + return { procesadas, filasEscritas, errores }; +} + +/** + * Itera todos los tenants activos y procesa sus invalidaciones pendientes. + * Usado por el cron job. + */ +export async function processAllTenantsInvalidations(): Promise<{ + tenantsRevisados: number; + totalProcesadas: number; + totalFilasEscritas: number; + totalErrores: number; +}> { + const tenants = await prisma.tenant.findMany({ + where: { active: true }, + select: { id: true, databaseName: true }, + }); + + let totalProcesadas = 0; + let totalFilasEscritas = 0; + let totalErrores = 0; + + for (const t of tenants) { + try { + const pool = await tenantDb.getPool(t.id, t.databaseName); + const r = await processInvalidations(pool, t.id); + totalProcesadas += r.procesadas; + totalFilasEscritas += r.filasEscritas; + totalErrores += r.errores; + } catch (err: any) { + console.error(`[Metricas] Error procesando tenant ${t.id}:`, err?.message || err); + totalErrores++; + } + } + + return { + tenantsRevisados: tenants.length, + totalProcesadas, + totalFilasEscritas, + totalErrores, + }; +} diff --git a/apps/api/src/services/metricas.service.ts b/apps/api/src/services/metricas.service.ts new file mode 100644 index 0000000..57f7867 --- /dev/null +++ b/apps/api/src/services/metricas.service.ts @@ -0,0 +1,225 @@ +import type { Pool } from 'pg'; + +export interface MetricaMensual { + anio: number; + mes: number; + regimenFiscal: string | null; + ivaTrasladado16: number; + ivaTrasladado8: number; + ivaTrasladado0: number; + ivaTrasladadoExento: number; + ivaTrasladadoTotal: number; + ivaAcreditable: number; + ivaRetenidoCobrado: number; + ivaRetenidoPagado: number; + ivaResultado: number; + ivaAFavorMes: number; + isrIngresosBrutos: number; + isrDeduccionesAutoriz: number; + isrBase: number; + isrCausado: number; + isrRetenido: number; + isrAPagar: number; + cfdisEmitidosCount: number; + cfdisRecibidosCount: number; + cfdisCanceladosCount: number; + ingresosDevengados: number; + ingresosCobrados: number; + egresosDevengados: number; + egresosPagados: number; + utilidadDevengada: number; + utilidadRealizada: number; + flujoEntradas: number; + flujoSalidas: number; + flujoNeto: number; + cxcSaldoFinal: number; + cxpSaldoFinal: number; + // Surface-only — totales de notas de crédito tipo E PUE en el mes/régimen. + // No participan en cálculos de ingresos/deducciones (ya no se restan); se + // persisten para visibilidad y BI directo sin recomputar. + ncsEmitidas: number; + ncsRecibidas: number; + // Art. 27 fracción III LISR — facturas recibidas pagadas en efectivo > $2,000. + // Surface-only, no afecta deducciones (que ya las EXCLUYE). + gastosNoDeduciblesEfectivo: number; + cerrado: boolean; + computedAt: string; +} + +export async function getMetricasMensuales( + pool: Pool, + contribuyenteId: string, + anio: number, + regimenFiscal?: string +): Promise { + const currentYear = new Date().getFullYear(); + + if (anio < currentYear) { + // COLD: read pre-computed + const params: unknown[] = [contribuyenteId, anio]; + let query = ` + SELECT + anio, mes, + regimen_fiscal AS "regimenFiscal", + iva_trasladado_16 AS "ivaTrasladado16", + iva_trasladado_8 AS "ivaTrasladado8", + iva_trasladado_0 AS "ivaTrasladado0", + iva_trasladado_exento AS "ivaTrasladadoExento", + iva_trasladado_total AS "ivaTrasladadoTotal", + iva_acreditable AS "ivaAcreditable", + iva_retenido_cobrado AS "ivaRetenidoCobrado", + iva_retenido_pagado AS "ivaRetenidoPagado", + iva_resultado AS "ivaResultado", + iva_a_favor_mes AS "ivaAFavorMes", + isr_ingresos_brutos AS "isrIngresosBrutos", + isr_deducciones_autoriz AS "isrDeduccionesAutoriz", + isr_base AS "isrBase", + isr_causado AS "isrCausado", + isr_retenido AS "isrRetenido", + isr_a_pagar AS "isrAPagar", + cfdis_emitidos_count AS "cfdisEmitidosCount", + cfdis_recibidos_count AS "cfdisRecibidosCount", + cfdis_cancelados_count AS "cfdisCanceladosCount", + ingresos_devengados AS "ingresosDevengados", + ingresos_cobrados AS "ingresosCobrados", + egresos_devengados AS "egresosDevengados", + egresos_pagados AS "egresosPagados", + utilidad_devengada AS "utilidadDevengada", + utilidad_realizada AS "utilidadRealizada", + flujo_entradas AS "flujoEntradas", + flujo_salidas AS "flujoSalidas", + flujo_neto AS "flujoNeto", + cxc_saldo_final AS "cxcSaldoFinal", + cxp_saldo_final AS "cxpSaldoFinal", + ncs_emitidas AS "ncsEmitidas", + ncs_recibidas AS "ncsRecibidas", + gastos_no_deducibles_efectivo AS "gastosNoDeduciblesEfectivo", + cerrado, + computed_at AS "computedAt" + FROM metricas_mensuales + WHERE contribuyente_id = $1 AND anio = $2 + `; + if (regimenFiscal) { + query += ' AND regimen_fiscal = $3'; + params.push(regimenFiscal); + } + query += ' ORDER BY mes'; + + const { rows } = await pool.query(query, params); + return rows.map(r => ({ ...r, cerrado: r.cerrado ?? false, computedAt: r.computedAt?.toISOString?.() ?? '' })); + } + + // HOT: current year — return empty (caller should use dashboard/impuestos services for on-the-fly computation) + return []; +} + +export async function markForInvalidation( + pool: Pool, + contribuyenteId: string, + anio: number, + mes: number, + reason: string +): Promise { + await pool.query(` + INSERT INTO metricas_invalidaciones (contribuyente_id, anio, mes, reason) + VALUES ($1, $2, $3, $4) + ON CONFLICT (contribuyente_id, anio, mes) DO UPDATE SET marcado_at = now(), reason = $4 + `, [contribuyenteId, anio, mes, reason]); +} + +export async function getPendingInvalidations(pool: Pool): Promise> { + const { rows } = await pool.query(` + SELECT contribuyente_id AS "contribuyenteId", anio, mes, reason + FROM metricas_invalidaciones + ORDER BY anio, mes + `); + return rows; +} + +export async function clearInvalidation(pool: Pool, contribuyenteId: string, anio: number, mes: number): Promise { + await pool.query( + 'DELETE FROM metricas_invalidaciones WHERE contribuyente_id = $1 AND anio = $2 AND mes = $3', + [contribuyenteId, anio, mes] + ); +} + +export async function upsertMetricaMensual( + pool: Pool, + contribuyenteId: string, + anio: number, + mes: number, + regimenFiscal: string | null, + data: Partial +): Promise { + await pool.query(` + INSERT INTO metricas_mensuales ( + contribuyente_id, anio, mes, regimen_fiscal, + iva_trasladado_total, iva_acreditable, iva_retenido_cobrado, iva_retenido_pagado, iva_resultado, + isr_ingresos_brutos, isr_deducciones_autoriz, isr_base, isr_causado, isr_retenido, isr_a_pagar, + cfdis_emitidos_count, cfdis_recibidos_count, cfdis_cancelados_count, + ingresos_devengados, ingresos_cobrados, egresos_devengados, egresos_pagados, + utilidad_devengada, utilidad_realizada, + flujo_entradas, flujo_salidas, flujo_neto, + cxc_saldo_final, cxp_saldo_final, + ncs_emitidas, ncs_recibidas, + gastos_no_deducibles_efectivo, + computed_at, source_max_cfdi_at + ) VALUES ( + $1, $2, $3, $4, + $5, $6, $7, $8, $9, + $10, $11, $12, $13, $14, $15, + $16, $17, $18, + $19, $20, $21, $22, + $23, $24, + $25, $26, $27, + $28, $29, + $30, $31, + $32, + now(), now() + ) + ON CONFLICT (contribuyente_id, anio, mes, regimen_fiscal) + DO UPDATE SET + iva_trasladado_total = $5, iva_acreditable = $6, iva_retenido_cobrado = $7, iva_retenido_pagado = $8, iva_resultado = $9, + isr_ingresos_brutos = $10, isr_deducciones_autoriz = $11, isr_base = $12, + isr_causado = $13, isr_retenido = $14, isr_a_pagar = $15, + cfdis_emitidos_count = $16, cfdis_recibidos_count = $17, cfdis_cancelados_count = $18, + ingresos_devengados = $19, ingresos_cobrados = $20, egresos_devengados = $21, egresos_pagados = $22, + utilidad_devengada = $23, utilidad_realizada = $24, + flujo_entradas = $25, flujo_salidas = $26, flujo_neto = $27, + cxc_saldo_final = $28, cxp_saldo_final = $29, + ncs_emitidas = $30, ncs_recibidas = $31, + gastos_no_deducibles_efectivo = $32, + computed_at = now(), source_max_cfdi_at = now() + `, [ + contribuyenteId, anio, mes, regimenFiscal, + data.ivaTrasladadoTotal ?? 0, data.ivaAcreditable ?? 0, data.ivaRetenidoCobrado ?? 0, data.ivaRetenidoPagado ?? 0, data.ivaResultado ?? 0, + data.isrIngresosBrutos ?? 0, data.isrDeduccionesAutoriz ?? 0, data.isrBase ?? 0, + data.isrCausado ?? 0, data.isrRetenido ?? 0, data.isrAPagar ?? 0, + data.cfdisEmitidosCount ?? 0, data.cfdisRecibidosCount ?? 0, data.cfdisCanceladosCount ?? 0, + data.ingresosDevengados ?? 0, data.ingresosCobrados ?? 0, data.egresosDevengados ?? 0, data.egresosPagados ?? 0, + data.utilidadDevengada ?? 0, data.utilidadRealizada ?? 0, + data.flujoEntradas ?? 0, data.flujoSalidas ?? 0, data.flujoNeto ?? 0, + data.cxcSaldoFinal ?? 0, data.cxpSaldoFinal ?? 0, + data.ncsEmitidas ?? 0, data.ncsRecibidas ?? 0, + data.gastosNoDeduciblesEfectivo ?? 0, + ]); +} + +export async function closeMonth(pool: Pool, anio: number, mes: number): Promise { + await pool.query( + 'UPDATE metricas_mensuales SET cerrado = true WHERE anio = $1 AND mes = $2', + [anio, mes] + ); +} + +export async function closeYear(pool: Pool, anio: number): Promise { + await pool.query( + 'UPDATE metricas_mensuales SET cerrado = true WHERE anio = $1', + [anio] + ); +} diff --git a/apps/api/src/services/notification-preferences.service.ts b/apps/api/src/services/notification-preferences.service.ts new file mode 100644 index 0000000..0b3fc54 --- /dev/null +++ b/apps/api/src/services/notification-preferences.service.ts @@ -0,0 +1,110 @@ +import type { Pool } from 'pg'; + +/** + * Tipos de correos informativos cuyo envío puede desactivarse por + * contribuyente. NO incluye correos transaccionales críticos + * (welcome, password-reset, payment-*) — esos siempre se envían. + * + * Estado de implementación: + * - documento_subido: ✅ implementado (notify-upload.service.ts) + * - weekly_update: ⏳ pendiente (job es tenant-wide hoy) + * - subscription_expiring: ⏳ pendiente (no es per-contribuyente hoy) + * - recordatorio_fiscal: ⏳ placeholder para futuras alertas + */ +export const EMAIL_TYPES = [ + 'documento_subido', + 'weekly_update', + 'subscription_expiring', + 'recordatorio_fiscal', +] as const; + +export type EmailType = (typeof EMAIL_TYPES)[number]; + +export type EmailPreferences = Record; + +/** + * Default: todo activado. Si el JSONB en BD viene vacío o falta una + * key, asumimos `true` para preservar el comportamiento previo. + */ +function applyDefaults(raw: Partial>): EmailPreferences { + const out = {} as EmailPreferences; + for (const t of EMAIL_TYPES) { + out[t] = raw[t] === false ? false : true; + } + return out; +} + +function sanitizeUuid(id: string): string { + return id.replace(/[^a-f0-9-]/gi, ''); +} + +/** + * Lee las preferencias de un contribuyente. Devuelve defaults (todo + * activado) si no hay fila o la columna está vacía. + */ +export async function getContribuyenteEmailPreferences( + pool: Pool, + contribuyenteId: string, +): Promise { + const safeId = sanitizeUuid(contribuyenteId); + const { rows } = await pool.query<{ email_preferences: Record | null }>( + `SELECT email_preferences FROM contribuyentes WHERE entidad_id = $1`, + [safeId], + ); + const raw = rows[0]?.email_preferences ?? {}; + return applyDefaults(raw); +} + +/** + * Actualiza las preferencias de un contribuyente. Solo persiste las + * keys conocidas (filtra extras maliciosos). Merge sobre la columna + * existente (no sobreescribe keys no enviadas). + */ +export async function setContribuyenteEmailPreferences( + pool: Pool, + contribuyenteId: string, + partial: Partial, +): Promise { + const safeId = sanitizeUuid(contribuyenteId); + const merged: Record = {}; + for (const t of EMAIL_TYPES) { + if (t in partial) merged[t] = partial[t] === true; + } + + await pool.query( + `UPDATE contribuyentes + SET email_preferences = COALESCE(email_preferences, '{}'::jsonb) || $2::jsonb + WHERE entidad_id = $1`, + [safeId, JSON.stringify(merged)], + ); + + return getContribuyenteEmailPreferences(pool, contribuyenteId); +} + +/** + * Lee preferencias para múltiples contribuyentes en una sola query. + * Útil para la UI de `/configuracion/notificaciones` que lista todos. + */ +export async function getEmailPreferencesPorContribuyente( + pool: Pool, +): Promise> { + const { rows } = await pool.query<{ + entidad_id: string; + rfc: string; + nombre: string; + email_preferences: Record | null; + }>( + `SELECT c.entidad_id, c.rfc, e.nombre, c.email_preferences + FROM contribuyentes c + JOIN entidades_gestionadas e ON e.id = c.entidad_id + WHERE e.active = true + ORDER BY e.nombre`, + ); + + return rows.map(r => ({ + contribuyenteId: r.entidad_id, + rfc: r.rfc, + nombre: r.nombre, + preferences: applyDefaults(r.email_preferences ?? {}), + })); +} diff --git a/apps/api/src/services/notifications.service.ts b/apps/api/src/services/notifications.service.ts new file mode 100644 index 0000000..f5df95b --- /dev/null +++ b/apps/api/src/services/notifications.service.ts @@ -0,0 +1,390 @@ +/** + * Notificaciones email automáticas (Option B — por evento). + * + * Cron diario 8:30 AM (`notifications.job.ts`) llama a las dos funciones + * principales de este servicio para cada tenant activo: + * + * - `processNewAlertas(pool, tenantId)`: detecta alertas que aparecen por + * primera vez (no están en `alertas_notificadas`) y manda un email + * batched al supervisor + auxiliares + clientes del contribuyente. + * Las alertas que dejaron de estar activas se marcan `resuelta_at`. + * + * - `processProximosRecordatorios(pool, tenantId)`: detecta recordatorios + * cuya `fecha_limite` cae en las ventanas 3 días / 1 día / mismo día + * y manda email a los responsables (cliente + auxiliar; si no hay + * auxiliar también supervisor; si owner es supervisor sin auxiliares + * también owner). Cada ventana se envía una sola vez (columnas + * `email_3d_at`, `email_1d_at`, `email_0d_at`). + * + * Decisión MVP: una alerta solo se notifica una vez. Si vuelve a activarse + * después de resolverse, no re-notifica (sería opt-in al borrar la fila + * cuando `resuelta_at` se setea). + */ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; +import { generarAlertasAutomaticas, type AlertaAuto } from './alertas-auto.service.js'; +import { emailService } from './email/email.service.js'; +import type { AlertaItem } from './email/templates/alertas-nuevas.js'; +import type { VentanaRecordatorio } from './email/templates/recordatorio-proximo.js'; + +const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:3000'; + +// ──────────────────────────────────────────────────────────────────────── +// Resolución de destinatarios +// ──────────────────────────────────────────────────────────────────────── + +interface UserContact { + userId: string; + email: string; + active: boolean; +} + +/** + * Resuelve user IDs ligados a un contribuyente (supervisor + auxiliares de + * carteras donde aparece + clientes con acceso). Retorna lista deduplicada. + */ +async function getUserIdsContribuyente( + pool: Pool, + contribuyenteId: string, +): Promise<{ supervisor: string | null; auxiliares: string[]; clientes: string[] }> { + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + const { rows } = await pool.query<{ + supervisor_user_id: string | null; + auxiliar_user_ids: string[]; + cliente_user_ids: string[]; + }>(` + SELECT + eg.supervisor_user_id, + COALESCE(( + SELECT array_agg(DISTINCT c.auxiliar_user_id) FILTER (WHERE c.auxiliar_user_id IS NOT NULL) + FROM cartera_entidades ce + JOIN carteras c ON c.id = ce.cartera_id + WHERE ce.entidad_id = eg.id + ), ARRAY[]::uuid[]) AS auxiliar_user_ids, + COALESCE(( + SELECT array_agg(DISTINCT user_id) FROM cliente_accesos WHERE entidad_id = eg.id + ), ARRAY[]::uuid[]) AS cliente_user_ids + FROM entidades_gestionadas eg + WHERE eg.id = $1::uuid + `, [safeId]); + + if (rows.length === 0) { + return { supervisor: null, auxiliares: [], clientes: [] }; + } + const r = rows[0]; + return { + supervisor: r.supervisor_user_id ?? null, + auxiliares: r.auxiliar_user_ids ?? [], + clientes: r.cliente_user_ids ?? [], + }; +} + +/** Owners activos del tenant (BD central). */ +async function getOwnerUserIds(tenantId: string): Promise { + const owners = await prisma.tenantMembership.findMany({ + where: { tenantId, isOwner: true, active: true }, + select: { userId: true }, + }); + return owners.map(o => o.userId); +} + +/** Resuelve emails para una lista de userIds; filtra inactivos. */ +async function getUserContacts(userIds: string[]): Promise { + if (userIds.length === 0) return []; + const users = await prisma.user.findMany({ + where: { id: { in: userIds }, active: true }, + select: { id: true, email: true, active: true }, + }); + return users.map(u => ({ userId: u.id, email: u.email, active: u.active })); +} + +/** + * Destinatarios de una alerta: supervisor + auxiliares + clientes del + * contribuyente. Si el owner del tenant es supervisor, ya queda incluido + * (no se duplica). + */ +async function recipientsForAlerta( + pool: Pool, + tenantId: string, + contribuyenteId: string, +): Promise { + const ids = await getUserIdsContribuyente(pool, contribuyenteId); + const userIds = new Set(); + if (ids.supervisor) userIds.add(ids.supervisor); + ids.auxiliares.forEach(id => userIds.add(id)); + ids.clientes.forEach(id => userIds.add(id)); + const contacts = await getUserContacts([...userIds]); + return [...new Set(contacts.map(c => c.email))]; +} + +/** + * Destinatarios de un recordatorio. Los recordatorios del despacho son + * tenant-level (no atados a contribuyente). Para públicos: clientes con + * algún acceso + auxiliares de cualquier cartera; si no hay auxiliares, + * supervisores; si owner aparece como supervisor, también recibe. + * + * Privados: solo el creador. + */ +async function recipientsForRecordatorio( + pool: Pool, + tenantId: string, + recordatorio: { creadoPor: string; privado: boolean }, +): Promise { + if (recordatorio.privado) { + const contacts = await getUserContacts([recordatorio.creadoPor]); + return [...new Set(contacts.map(c => c.email))]; + } + + // Recordatorio público: lee universos relevantes del tenant. + const { rows: [r] } = await pool.query<{ + auxiliar_user_ids: string[]; + supervisor_user_ids: string[]; + cliente_user_ids: string[]; + }>(` + SELECT + COALESCE(( + SELECT array_agg(DISTINCT auxiliar_user_id) + FROM carteras WHERE auxiliar_user_id IS NOT NULL + ), ARRAY[]::uuid[]) AS auxiliar_user_ids, + COALESCE(( + SELECT array_agg(DISTINCT supervisor_user_id) FROM ( + SELECT supervisor_user_id FROM entidades_gestionadas WHERE supervisor_user_id IS NOT NULL + UNION + SELECT supervisor_user_id FROM carteras WHERE supervisor_user_id IS NOT NULL + ) sup + ), ARRAY[]::uuid[]) AS supervisor_user_ids, + COALESCE(( + SELECT array_agg(DISTINCT user_id) FROM cliente_accesos + ), ARRAY[]::uuid[]) AS cliente_user_ids + `); + + const auxiliares = r?.auxiliar_user_ids ?? []; + const supervisores = r?.supervisor_user_ids ?? []; + const clientes = r?.cliente_user_ids ?? []; + const owners = await getOwnerUserIds(tenantId); + + // Regla del owner: clientes y auxiliares siempre. Si no hay auxiliares, + // agregar supervisores. Si owner es supervisor y no hay auxiliares, + // owner queda incluido vía la lista de supervisores. + const userIds = new Set(); + clientes.forEach(id => userIds.add(id)); + auxiliares.forEach(id => userIds.add(id)); + if (auxiliares.length === 0) { + supervisores.forEach(id => userIds.add(id)); + // Solo si owner aparece como supervisor (intersección): + for (const ownerId of owners) { + if (supervisores.includes(ownerId)) userIds.add(ownerId); + } + } + + const contacts = await getUserContacts([...userIds]); + return [...new Set(contacts.map(c => c.email))]; +} + +// ──────────────────────────────────────────────────────────────────────── +// Procesamiento de alertas +// ──────────────────────────────────────────────────────────────────────── + +interface ContribuyenteInfo { + entidadId: string; + rfc: string; + nombre: string; +} + +/** Lista contribuyentes activos del tenant. */ +async function listContribuyentes(pool: Pool): Promise { + const { rows } = await pool.query<{ entidad_id: string; rfc: string; nombre: string }>(` + SELECT eg.id AS entidad_id, c.rfc, eg.nombre + FROM entidades_gestionadas eg + JOIN contribuyentes c ON c.entidad_id = eg.id + WHERE eg.active = true AND eg.tipo = 'CONTRIBUYENTE' + `); + return rows.map(r => ({ entidadId: r.entidad_id, rfc: r.rfc, nombre: r.nombre })); +} + +function mapAlertaToItem(a: AlertaAuto): AlertaItem { + return { + alertaId: a.id, + nivel: a.prioridad === 'alta' ? 'high' : a.prioridad === 'media' ? 'medium' : 'low', + titulo: a.titulo, + mensaje: a.mensaje, + }; +} + +/** + * Para un (tenant, contribuyente): + * 1. Genera alertas activas vía `generarAlertasAutomaticas`. + * 2. Inserta filas nuevas en `alertas_notificadas` (ON CONFLICT DO NOTHING). + * 3. Marca como resueltas las alertas previamente notificadas que NO están + * activas hoy (UPDATE resuelta_at). + * 4. Si hay alertas nuevas, envía email batched a los responsables. + */ +async function processAlertasContribuyente( + pool: Pool, + tenantId: string, + tenant: { rfc: string; nombre: string }, + contribuyente: ContribuyenteInfo, +): Promise<{ nuevas: number; resueltas: number }> { + const alertasActivas = await generarAlertasAutomaticas(pool, tenantId, contribuyente.entidadId); + const activosIds = alertasActivas.map(a => a.id); + + // Re-notificación tras 30 días (D7, 2026-04-26): borra registros de + // alertas que estuvieron resueltas más de 30 días. Si la alerta vuelve + // a aparecer ahora, el INSERT siguiente la detecta como "nueva" y + // vuelve a notificar. Si nunca se resolvió (resuelta_at IS NULL) o se + // resolvió hace menos de 30 días, la fila se conserva y el INSERT no + // dispara email. + await pool.query(` + DELETE FROM alertas_notificadas + WHERE contribuyente_id = $1::uuid + AND resuelta_at IS NOT NULL + AND resuelta_at < NOW() - INTERVAL '30 days' + `, [contribuyente.entidadId]); + + // Detecta alertas nuevas: INSERT con ON CONFLICT DO NOTHING. RETURNING id + // solo trae las filas insertadas (no las que chocaron con el UNIQUE), + // así sabemos cuáles eran realmente nuevas. Tras la re-notificación de + // 30 días, una alerta puede volver a notificarse si reapareció después + // de >30 días resuelta. + const nuevas: AlertaAuto[] = []; + for (const a of alertasActivas) { + const { rows } = await pool.query<{ id: number }>(` + INSERT INTO alertas_notificadas (alerta_id, contribuyente_id) + VALUES ($1, $2::uuid) + ON CONFLICT (alerta_id, COALESCE(contribuyente_id::text, '')) DO NOTHING + RETURNING id + `, [a.id, contribuyente.entidadId]); + if (rows.length > 0) nuevas.push(a); + } + + // Marca como resueltas las alertas previamente notificadas que ya no + // aparecen activas hoy. Informativo (no genera email). + let resueltas = 0; + const updateQuery = activosIds.length > 0 + ? `UPDATE alertas_notificadas SET resuelta_at = NOW() + WHERE contribuyente_id = $1::uuid AND resuelta_at IS NULL + AND alerta_id <> ALL($2::text[])` + : `UPDATE alertas_notificadas SET resuelta_at = NOW() + WHERE contribuyente_id = $1::uuid AND resuelta_at IS NULL`; + const params: any[] = activosIds.length > 0 + ? [contribuyente.entidadId, activosIds] + : [contribuyente.entidadId]; + const { rowCount } = await pool.query(updateQuery, params); + resueltas = rowCount ?? 0; + + if (nuevas.length === 0) { + return { nuevas: 0, resueltas }; + } + + // Envía email batched a los responsables del contribuyente. + const recipients = await recipientsForAlerta(pool, tenantId, contribuyente.entidadId); + if (recipients.length === 0) { + console.warn(`[Notifications] Sin destinatarios para alertas de ${contribuyente.rfc} (tenant ${tenant.rfc})`); + return { nuevas: nuevas.length, resueltas }; + } + + await emailService.sendAlertasNuevas(recipients, { + contribuyenteRfc: contribuyente.rfc, + contribuyenteNombre: contribuyente.nombre, + despachoNombre: tenant.nombre, + alertas: nuevas.map(mapAlertaToItem), + link: `${FRONTEND_URL}/alertas`, + }); + + return { nuevas: nuevas.length, resueltas }; +} + +/** Procesa todas las alertas del tenant — itera contribuyentes activos. */ +export async function processNewAlertas( + pool: Pool, + tenantId: string, + tenant: { rfc: string; nombre: string }, +): Promise<{ contribuyentes: number; nuevasTotal: number }> { + const contribuyentes = await listContribuyentes(pool); + let nuevasTotal = 0; + for (const c of contribuyentes) { + try { + const { nuevas } = await processAlertasContribuyente(pool, tenantId, tenant, c); + nuevasTotal += nuevas; + } catch (err: any) { + console.error(`[Notifications] Error procesando alertas de ${c.rfc} (tenant ${tenant.rfc}):`, err.message || err); + } + } + return { contribuyentes: contribuyentes.length, nuevasTotal }; +} + +// ──────────────────────────────────────────────────────────────────────── +// Procesamiento de recordatorios próximos +// ──────────────────────────────────────────────────────────────────────── + +interface RecordatorioRow { + id: number; + titulo: string; + descripcion: string | null; + notas: string | null; + fecha_limite: string; + privado: boolean; + creado_por: string; + email_3d_at: Date | null; + email_1d_at: Date | null; + email_0d_at: Date | null; +} + +const VENTANA_DIAS: Record = { + '3d': 3, + '1d': 1, + '0d': 0, +}; + +/** + * Procesa recordatorios cuya `fecha_limite` cae en alguna ventana (3d/1d/0d) + * y que aún no tienen email enviado para esa ventana específica. Manda email + * y marca la columna correspondiente. + */ +export async function processProximosRecordatorios( + pool: Pool, + tenantId: string, + tenant: { rfc: string; nombre: string }, +): Promise<{ enviados: number }> { + let enviados = 0; + for (const ventana of (['3d', '1d', '0d'] as const)) { + const dias = VENTANA_DIAS[ventana]; + const col = `email_${ventana}_at`; + const { rows } = await pool.query(` + SELECT id, titulo, descripcion, notas, fecha_limite::text AS fecha_limite, + privado, creado_por, email_3d_at, email_1d_at, email_0d_at + FROM recordatorios + WHERE completado = false + AND fecha_limite = (CURRENT_DATE + ${dias})::date + AND ${col} IS NULL + `); + + for (const r of rows) { + try { + const recipients = await recipientsForRecordatorio(pool, tenantId, { + creadoPor: r.creado_por, + privado: r.privado, + }); + if (recipients.length === 0) { + console.warn(`[Notifications] Recordatorio ${r.id} (${tenant.rfc}) sin destinatarios — skip ${ventana}`); + continue; + } + await emailService.sendRecordatorioProximo(recipients, { + titulo: r.titulo, + descripcion: r.descripcion, + notas: r.notas, + fechaLimite: r.fecha_limite, + ventana, + despachoNombre: tenant.nombre, + link: `${FRONTEND_URL}/calendario`, + }); + // Marca columna de ventana enviada. + await pool.query(`UPDATE recordatorios SET ${col} = NOW() WHERE id = $1`, [r.id]); + enviados++; + } catch (err: any) { + console.error(`[Notifications] Error en recordatorio ${r.id} (${tenant.rfc}, ${ventana}):`, err.message || err); + } + } + } + return { enviados }; +} diff --git a/apps/api/src/services/notify-upload.service.ts b/apps/api/src/services/notify-upload.service.ts new file mode 100644 index 0000000..3e64805 --- /dev/null +++ b/apps/api/src/services/notify-upload.service.ts @@ -0,0 +1,90 @@ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; +import { emailService } from './email/email.service.js'; +import { getTenantOwnerEmails, getUserEmailById } from '../utils/memberships.js'; +import { env } from '../config/env.js'; +import { getContribuyenteEmailPreferences } from './notification-preferences.service.js'; +import type { DocumentoSubidoData } from './email/templates/documento-subido.js'; + +/** + * Notifica a los destinatarios relevantes cuando se sube una declaración + * o un documento extra. Destinatarios: + * - Owners activos del despacho (getTenantOwnerEmails) + * - Supervisor del contribuyente (entidades_gestionadas.supervisor_user_id), + * si existe y no coincide con un owner ya incluido + * + * El uploader mismo SE EXCLUYE (no tiene sentido notificarle su propia acción). + * + * Fire-and-forget: el caller hace `.catch()` y esta función no re-lanza. + * Fail-soft: si SMTP no está configurado, los envíos se loguean a consola + * vía el transport de @horux/core. + */ +export async function notifyDocumentoSubido(params: { + pool: Pool; + tenantId: string; + contribuyenteId: string | null; + subidoPor: string; + kind: DocumentoSubidoData['kind']; + declaracion?: DocumentoSubidoData['declaracion']; + extra?: DocumentoSubidoData['extra']; +}): Promise { + const { pool, tenantId, contribuyenteId, subidoPor } = params; + + // 1. Datos del contribuyente (desde BD tenant). Sin contribuyenteId no hay + // subject informativo ni supervisor — skip. + if (!contribuyenteId) return; + + // Respeta preferencias de notificación del contribuyente. Si el user + // desactivó `documento_subido` para este contribuyente, no enviar. + const prefs = await getContribuyenteEmailPreferences(pool, contribuyenteId); + if (!prefs.documento_subido) return; + + const { rows } = await pool.query<{ + rfc: string; + nombre: string; + supervisor_user_id: string | null; + }>( + `SELECT c.rfc, eg.nombre, eg.supervisor_user_id + FROM contribuyentes c + JOIN entidades_gestionadas eg ON eg.id = c.entidad_id + WHERE c.entidad_id = $1`, + [contribuyenteId.replace(/[^a-f0-9-]/gi, '')], + ); + if (rows.length === 0) return; + const contrib = rows[0]; + + // 2. Recipients. Owners primero; luego supervisor si aplica. + const owners = await getTenantOwnerEmails(tenantId); + const recipients = new Set(owners); + + if (contrib.supervisor_user_id) { + const supervisorEmail = await getUserEmailById(contrib.supervisor_user_id); + if (supervisorEmail) recipients.add(supervisorEmail); + } + + // Excluir al uploader: no notificarle su propia acción. + recipients.delete(subidoPor.toLowerCase()); + recipients.delete(subidoPor); + + if (recipients.size === 0) return; + + // 3. Datos del despacho (para mostrar en el body). + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { nombre: true }, + }); + + // 4. Link al sistema. Usa FRONTEND_URL del env. + const link = `${env.FRONTEND_URL}/documentos`; + + await emailService.sendDocumentoSubido(Array.from(recipients), { + kind: params.kind, + subidoPor, + contribuyenteRfc: contrib.rfc, + contribuyenteNombre: contrib.nombre, + despachoNombre: tenant?.nombre, + declaracion: params.declaracion, + extra: params.extra, + link, + }); +} diff --git a/apps/api/src/services/obligaciones.service.ts b/apps/api/src/services/obligaciones.service.ts new file mode 100644 index 0000000..1f9c36b --- /dev/null +++ b/apps/api/src/services/obligaciones.service.ts @@ -0,0 +1,492 @@ +import type { Pool } from 'pg'; +import { OBLIGACIONES_CATALOGO, getRecomendaciones, type ObligacionFiscal } from '../constants/obligaciones-fiscales.js'; + +/** + * Keyword-based matching: each catalog entry has discriminant keywords + * that must ALL appear in the SAT description (normalized, lowercase, no accents). + * Multiple keyword sets per entry allow for variant phrasings. + */ +const CATALOG_MATCH_RULES: Array<{ id: string; keywords: string[][] }> = [ + // ISR provisionales + { id: 'isr-provisional', keywords: [ + ['pago provisional', 'isr', 'actividades empresariales'], + ['pago provisional', 'isr personas morales', 'general'], + ['pago provisional mensual de isr personas morales'], + ]}, + { id: 'isr-resico-pm', keywords: [ + ['isr', 'simplificado de confianza', 'pago provisional'], + ['isr', 'simplificado de confianza', 'pago provisional mensual'], + ]}, + { id: 'isr-resico-pf', keywords: [ + ['isr', 'simplificado de confianza', 'ajuste anual'], + // Note: PF RESICO "pago provisional" is same id as PM; differentiate by RFC length at runtime + ]}, + + // IVA + { id: 'iva-mensual', keywords: [ + ['pago definitivo', 'iva', 'mensual'], + ['pago definitivo mensual de iva'], + ]}, + + // DIOT + { id: 'diot', keywords: [ + ['proveedores', 'iva'], + ['diot'], + ]}, + + // Anuales + { id: 'anual-isr-pm', keywords: [ + ['declaracion anual', 'isr', 'personas morales'], + ['anual de isr del regimen', 'simplificado', 'personas morales'], + ]}, + { id: 'anual-isr-pf', keywords: [ + ['declaracion anual', 'isr', 'personas fisicas'], + ['ajuste anual', 'isr', 'declaracion anual', 'simplificado'], + ]}, + + // Retenciones ISR + { id: 'ret-isr-sueldos', keywords: [ + ['retenciones', 'isr', 'sueldos y salarios'], + ['retenciones mensuales de isr por sueldos'], + ]}, + { id: 'ret-isr-asimilados', keywords: [ + ['retenciones', 'isr', 'asimilados a salarios'], + ['retenciones mensuales de isr por ingresos asimilados'], + ]}, + { id: 'ret-isr-honorarios', keywords: [ + ['retencion', 'isr', 'servicios profesionales'], + ['retenciones de isr por servicios profesionales'], // TPR variant (missing accent) + ]}, + + // Retenciones IVA + { id: 'ret-iva', keywords: [ + ['retenciones de iva'], + ['retenciones', 'iva', 'mensual'], + ]}, + + // IEPS + { id: 'ieps', keywords: [ + ['ieps'], + ]}, + + // RIF bimestral + { id: 'isr-provisional', keywords: [ + ['bimestral del rif'], + ['pago definitivo bimestral del rif'], + ]}, + + // Arrendamiento + { id: 'isr-provisional', keywords: [ + ['isr', 'arrendamiento de inmuebles', 'pago provisional'], + ['isr por arrendamiento de inmuebles pf'], + ]}, + + // Informativas (no tienen match directo en catálogo pero agrupar con DIM) + { id: 'dim', keywords: [ + ['declaracion informativa anual', 'pagos y retenciones'], + ['declaracion informativa anual de clientes y proveedores'], + ['declaracion informativa anual de retenciones'], + ['declaracion informativa de iva con la anual'], + ]}, +]; + +function normalizeForMatch(s: string): string { + return s + .normalize('NFD').replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/[.,;:()]/g, '') + .replace(/\s+/g, ' ') + .trim(); +} + +function matchCsfToCatalog(descripcion: string, rfc: string): ObligacionFiscal | undefined { + const norm = normalizeForMatch(descripcion); + const esPM = rfc.length === 12; + + for (const rule of CATALOG_MATCH_RULES) { + for (const kwSet of rule.keywords) { + const allMatch = kwSet.every(kw => norm.includes(normalizeForMatch(kw))); + if (allMatch) { + // Special case: RESICO ISR provisional — PM vs PF + if (rule.id === 'isr-resico-pm' && !esPM) { + return OBLIGACIONES_CATALOGO.find(c => c.id === 'isr-resico-pf'); + } + if (rule.id === 'isr-resico-pf' && esPM) { + return OBLIGACIONES_CATALOGO.find(c => c.id === 'isr-resico-pm'); + } + return OBLIGACIONES_CATALOGO.find(c => c.id === rule.id); + } + } + } + return undefined; +} + +export interface ObligacionContribuyente { + id: string; + contribuyenteId: string; + catalogoId: string | null; + nombre: string; + fundamento: string | null; + frecuencia: string | null; + fechaLimite: string | null; + categoria: string | null; + activa: boolean; + esRecomendada: boolean; + esCustom: boolean; + completada: boolean; + completadaAt: string | null; + completadaPor: string | null; + periodoCompletado: string | null; + createdAt?: string; +} + +export function getCatalogo(): ObligacionFiscal[] { + return OBLIGACIONES_CATALOGO; +} + +export async function getObligaciones(pool: Pool, contribuyenteId: string): Promise { + const { rows } = await pool.query(` + SELECT id, contribuyente_id AS "contribuyenteId", catalogo_id AS "catalogoId", + nombre, fundamento, frecuencia, fecha_limite AS "fechaLimite", categoria, + activa, es_recomendada AS "esRecomendada", es_custom AS "esCustom", + completada, completada_at AS "completadaAt", completada_por AS "completadaPor", + periodo_completado AS "periodoCompletado", + created_at AS "createdAt" + FROM obligaciones_contribuyente + WHERE contribuyente_id = $1 + ORDER BY categoria, nombre + `, [contribuyenteId]); + return rows; +} + +/** + * Reads obligations from the latest Constancia de Situación Fiscal (CSF) + * and populates obligaciones_contribuyente. Falls back to catalog-based + * recommendations if no CSF exists. + */ +export async function initRecomendaciones( + pool: Pool, + contribuyenteId: string, + rfc: string, + regimenes: string[], + tieneNomina: boolean +): Promise { + // Clean up alerts and periodos for existing recommended obligations before replacing + await pool.query( + `DELETE FROM alertas WHERE tipo LIKE 'ob-%' AND SUBSTRING(tipo FROM 4 FOR 36) IN ( + SELECT id::text FROM obligaciones_contribuyente WHERE contribuyente_id = $1 AND es_recomendada = true + )`, + [contribuyenteId], + ); + await pool.query( + `DELETE FROM obligacion_periodos WHERE obligacion_id IN ( + SELECT id FROM obligaciones_contribuyente WHERE contribuyente_id = $1 AND es_recomendada = true + )`, + [contribuyenteId], + ); + // Clear previous recommended obligations (re-init replaces them) + await pool.query( + `DELETE FROM obligaciones_contribuyente WHERE contribuyente_id = $1 AND es_recomendada = true`, + [contribuyenteId], + ); + + // Try to get obligations from the latest CSF + const { rows: csfRows } = await pool.query(` + SELECT datos->'obligaciones' as obligaciones + FROM constancias_situacion_fiscal + WHERE rfc = $1 + ORDER BY created_at DESC LIMIT 1 + `, [rfc]); + + const csfObligaciones = csfRows[0]?.obligaciones as Array<{ + descripcion: string; + fechaInicio: string; + fechaFin?: string; + descripcionVencimiento: string; + }> | null; + + let count = 0; + + if (csfObligaciones && csfObligaciones.length > 0) { + // Use CSF obligations directly — these are the official SAT obligations + // Only import ACTIVE obligations (no fechaFin = still in effect) + const activeCsf = csfObligaciones.filter(ob => !ob.fechaFin); + for (const ob of activeCsf) { + // Keyword-based matching against catalog for enrichment (fundamento, categoria) + const catalogMatch = matchCsfToCatalog(ob.descripcion, rfc); + + const { rowCount } = await pool.query(` + INSERT INTO obligaciones_contribuyente ( + contribuyente_id, catalogo_id, nombre, fundamento, frecuencia, fecha_limite, categoria, es_recomendada + ) VALUES ($1, $2, $3, $4, $5, $6, $7, true) + ON CONFLICT DO NOTHING + `, [ + contribuyenteId, + catalogMatch?.id || null, + ob.descripcion, + catalogMatch?.fundamento || null, + catalogMatch?.frecuencia || inferirFrecuencia(ob.descripcionVencimiento), + ob.descripcionVencimiento, + catalogMatch?.categoria || 'SAT', + ]); + count += rowCount ?? 0; + } + } else { + // Fallback: use catalog-based recommendations + const recomendadas = getRecomendaciones(rfc, regimenes, tieneNomina); + for (const ob of recomendadas) { + const { rowCount } = await pool.query(` + INSERT INTO obligaciones_contribuyente (contribuyente_id, catalogo_id, nombre, fundamento, frecuencia, fecha_limite, categoria, es_recomendada) + VALUES ($1, $2, $3, $4, $5, $6, $7, true) + ON CONFLICT DO NOTHING + `, [contribuyenteId, ob.id, ob.nombre, ob.fundamento, ob.frecuencia, ob.fechaLimite, ob.categoria]); + count += rowCount ?? 0; + } + } + + return count; +} + +function inferirFrecuencia(vencimiento: string): string { + const lower = vencimiento.toLowerCase(); + if (lower.includes('mensual') || lower.includes('mes')) return 'mensual'; + if (lower.includes('bimest')) return 'bimestral'; + if (lower.includes('trimest')) return 'trimestral'; + if (lower.includes('anual') || lower.includes('ejercicio') || lower.includes('tres meses siguientes')) return 'anual'; + return 'mensual'; +} + +export async function completeObligacion(pool: Pool, obligacionId: string, userId: string, periodo: string): Promise { + const { rowCount } = await pool.query( + 'UPDATE obligaciones_contribuyente SET completada = true, completada_at = now(), completada_por = $2, periodo_completado = $3 WHERE id = $1', + [obligacionId, userId, periodo] + ); + return (rowCount ?? 0) > 0; +} + +export async function uncompleteObligacion(pool: Pool, obligacionId: string): Promise { + const { rowCount } = await pool.query( + 'UPDATE obligaciones_contribuyente SET completada = false, completada_at = null, completada_por = null, periodo_completado = null WHERE id = $1', + [obligacionId] + ); + return (rowCount ?? 0) > 0; +} + +export async function addObligacion(pool: Pool, contribuyenteId: string, data: { + catalogoId?: string; + nombre: string; + fundamento?: string; + frecuencia?: string; + fechaLimite?: string; + categoria?: string; +}): Promise { + const isFromCatalog = !!data.catalogoId; + const { rows: [row] } = await pool.query(` + INSERT INTO obligaciones_contribuyente (contribuyente_id, catalogo_id, nombre, fundamento, frecuencia, fecha_limite, categoria, es_custom) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING id, contribuyente_id AS "contribuyenteId", catalogo_id AS "catalogoId", + nombre, fundamento, frecuencia, fecha_limite AS "fechaLimite", categoria, + activa, es_recomendada AS "esRecomendada", es_custom AS "esCustom" + `, [contribuyenteId, data.catalogoId || null, data.nombre, data.fundamento || null, + data.frecuencia || null, data.fechaLimite || null, data.categoria || 'Custom', + !isFromCatalog]); + return row; +} + +export async function removeObligacion(pool: Pool, obligacionId: string): Promise { + const { rowCount } = await pool.query( + 'UPDATE obligaciones_contribuyente SET activa = false WHERE id = $1', + [obligacionId] + ); + // Clean up alerts generated for this obligation (tipo format: 'ob-{obligacionId}-{periodo}') + await pool.query( + `DELETE FROM alertas WHERE tipo LIKE $1`, + [`ob-${obligacionId}-%`], + ); + // Clean up completion records + await pool.query( + 'DELETE FROM obligacion_periodos WHERE obligacion_id = $1', + [obligacionId], + ); + return (rowCount ?? 0) > 0; +} + +export async function restoreObligacion(pool: Pool, obligacionId: string): Promise { + const { rowCount } = await pool.query( + 'UPDATE obligaciones_contribuyente SET activa = true WHERE id = $1', + [obligacionId] + ); + return (rowCount ?? 0) > 0; +} + +/** + * Returns obligations for a specific period (YYYY-MM) for a contribuyente. + * Includes: + * - All active obligations that apply to this period (based on frequency) + * - Completion status from obligacion_periodos table + * - Past-due obligations from previous periods that were NOT completed + */ +export interface DeclaracionLink { + id: number; + año: number; + mes: number; + tipo: 'normal' | 'complementaria'; + pdfFilename: string | null; +} + +export async function getObligacionesPorPeriodo( + pool: Pool, + contribuyenteId: string, + periodo: string, // "2026-04" + incluirAtrasados: boolean = true +): Promise> { + // Get all active obligations for this contribuyente + const obligaciones = await getObligaciones(pool, contribuyenteId); + const activas = obligaciones.filter(o => o.activa); + + const [year, month] = periodo.split('-').map(Number); + const currentPeriodo = new Date().toISOString().substring(0, 7); + const results: Array = []; + + // Get all completion records + associated declaration info for this contribuyente + const { rows: completions } = await pool.query<{ + obligacion_id: string; + periodo: string; + completada: boolean; + declaracion_id: number | null; + decl_año: number | null; + decl_mes: number | null; + decl_tipo: 'normal' | 'complementaria' | null; + decl_pdf_filename: string | null; + }>(` + SELECT op.obligacion_id, op.periodo, op.completada, + op.declaracion_id, + dp.año AS decl_año, + dp.mes AS decl_mes, + dp.tipo AS decl_tipo, + dp.pdf_filename AS decl_pdf_filename + FROM obligacion_periodos op + JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id + LEFT JOIN declaraciones_provisionales dp ON dp.id = op.declaracion_id + WHERE oc.contribuyente_id = $1 + `, [contribuyenteId]); + + const completionMap = new Map(); + const declaracionMap = new Map(); + for (const c of completions) { + const key = `${c.obligacion_id}:${c.periodo}`; + completionMap.set(key, c.completada); + if (c.declaracion_id && c.decl_año != null && c.decl_mes != null && c.decl_tipo) { + declaracionMap.set(key, { + id: c.declaracion_id, + año: c.decl_año, + mes: c.decl_mes, + tipo: c.decl_tipo, + pdfFilename: c.decl_pdf_filename, + }); + } + } + + for (const ob of activas) { + // Obligations only apply from the month they were created forward + const obStartPeriodo = ob.createdAt + ? new Date(ob.createdAt).toISOString().substring(0, 7) + : '2000-01'; + + // Check if this obligation applies to the requested period + if (periodo >= obStartPeriodo && appliesTo(ob.frecuencia, periodo)) { + const key = `${ob.id}:${periodo}`; + const isCompleted = completionMap.get(key) === true; + results.push({ + ...ob, + periodStatus: isCompleted ? 'completada' : 'pendiente', + periodoAplica: periodo, + declaracion: declaracionMap.get(key) ?? null, + }); + } + + // Check past-due (previous periods not completed) — only if requested + if (incluirAtrasados && periodo >= currentPeriodo) { + // Look back up to 12 months for overdue items + for (let i = 1; i <= 12; i++) { + let pm = month - i; + let py = year; + while (pm < 1) { pm += 12; py--; } + const pastPeriodo = `${py}-${String(pm).padStart(2, '0')}`; + + if (pastPeriodo >= currentPeriodo) continue; // only past periods + if (pastPeriodo < obStartPeriodo) continue; // don't go before obligation was created + if (!appliesTo(ob.frecuencia, pastPeriodo)) continue; + + const pastKey = `${ob.id}:${pastPeriodo}`; + const pastCompleted = completionMap.get(pastKey) === true; + + if (!pastCompleted) { + // Don't add duplicates + if (!results.find(r => r.id === ob.id && r.periodoAplica === pastPeriodo)) { + results.push({ + ...ob, + periodStatus: 'atrasada', + periodoAplica: pastPeriodo, + declaracion: null, + }); + } + } + } + } + } + + // Sort: atrasadas first, then by name + results.sort((a, b) => { + if (a.periodStatus === 'atrasada' && b.periodStatus !== 'atrasada') return -1; + if (b.periodStatus === 'atrasada' && a.periodStatus !== 'atrasada') return 1; + return a.nombre.localeCompare(b.nombre); + }); + + return results as Array; +} + +function appliesTo(frecuencia: string | null, periodo: string): boolean { + const [, month] = periodo.split('-').map(Number); + switch (frecuencia) { + case 'mensual': return true; + case 'bimestral': return month % 2 === 1; // Jan, Mar, May... + case 'trimestral': return [1, 4, 7, 10].includes(month); + case 'anual': return month === 3 || month === 4; // March (PM) or April (PF) — show in both + case 'eventual': return false; // Don't auto-show + default: return true; + } +} + +/** + * Mark an obligation as completed for a specific period + */ +export async function completePeriodo( + pool: Pool, + obligacionId: string, + periodo: string, + userId: string, + notas?: string +): Promise { + await pool.query(` + INSERT INTO obligacion_periodos (obligacion_id, periodo, completada, completada_at, completada_por, notas) + VALUES ($1, $2, true, now(), $3, $4) + ON CONFLICT (obligacion_id, periodo) + DO UPDATE SET completada = true, completada_at = now(), completada_por = $3, notas = COALESCE($4, obligacion_periodos.notas) + `, [obligacionId, periodo, userId, notas || null]); + return true; +} + +/** + * Unmark an obligation completion for a specific period + */ +export async function uncompletePeriodo( + pool: Pool, + obligacionId: string, + periodo: string +): Promise { + await pool.query(` + DELETE FROM obligacion_periodos WHERE obligacion_id = $1 AND periodo = $2 + `, [obligacionId, periodo]); + return true; +} diff --git a/apps/api/src/services/opinion-cumplimiento.service.ts b/apps/api/src/services/opinion-cumplimiento.service.ts new file mode 100644 index 0000000..ce6d37f --- /dev/null +++ b/apps/api/src/services/opinion-cumplimiento.service.ts @@ -0,0 +1,185 @@ +import { chromium } from 'playwright'; +import { writeFileSync, unlinkSync, mkdirSync, rmdirSync } from 'fs'; +import { join } from 'path'; +import { tmpdir } from 'os'; +import { randomUUID } from 'crypto'; +import type { Pool } from 'pg'; +import type { OpinionCumplimiento } from '@horux/shared'; +import { getDecryptedFiel } from './fiel.service.js'; +import { getDecryptedFielContribuyente } from './contribuyente-fiel.service.js'; +import { loginToSatOpinion } from './sat/sat-opinion-login.js'; +import { extractOpinionPdf } from './sat/sat-opinion-scraper.js'; +import { parseOpinionPdf } from './sat/sat-opinion-parser.js'; +import { prisma, tenantDb } from '../config/database.js'; + +const PROCESS_TIMEOUT = 180_000; // 3 minutes per tenant + +/** + * Downloads and stores the Opinión de Cumplimiento for a tenant. + */ +export async function consultarOpinion(tenantId: string): Promise { + const fiel = await getDecryptedFiel(tenantId); + if (!fiel) { + throw new Error('No hay FIEL configurada o está vencida'); + } + + const tempId = randomUUID(); + const tempDir = join(tmpdir(), `horux-fiel-${tempId}`); + mkdirSync(tempDir, { recursive: true, mode: 0o700 }); + + const cerPath = join(tempDir, 'cert.cer'); + const keyPath = join(tempDir, 'key.key'); + + try { + writeFileSync(cerPath, Buffer.from(fiel.cerContent, 'binary'), { mode: 0o600 }); + writeFileSync(keyPath, Buffer.from(fiel.keyContent, 'binary'), { mode: 0o600 }); + + const browser = await chromium.launch({ headless: true }); + + try { + const context = await browser.newContext({ + viewport: { width: 1280, height: 720 }, + }); + const page = await context.newPage(); + + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout: proceso de opinión excedió 3 minutos')), PROCESS_TIMEOUT) + ); + + const resultPromise = (async () => { + const reportPage = await loginToSatOpinion(page, cerPath, keyPath, fiel.password); + const pdfBuffer = await extractOpinionPdf(reportPage); + const parsed = await parseOpinionPdf(pdfBuffer); + + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { databaseName: true }, + }); + if (!tenant) throw new Error('Tenant no encontrado'); + + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + + const { rows } = await pool.query(` + INSERT INTO opiniones_cumplimiento (rfc, razon_social, estatus, folio, cadena_original, fecha_consulta, pdf) + VALUES ($1, $2, $3, $4, $5, NOW(), $6) + RETURNING id, rfc, razon_social as "razonSocial", estatus, folio, cadena_original as "cadenaOriginal", + fecha_consulta as "fechaConsulta", created_at as "createdAt" + `, [parsed.rfc, parsed.razonSocial, parsed.estatus, parsed.folio, parsed.cadenaOriginal, pdfBuffer]); + + return rows[0] as OpinionCumplimiento; + })(); + + return await Promise.race([resultPromise, timeoutPromise]); + } finally { + await browser.close(); + } + } finally { + try { unlinkSync(cerPath); } catch { /* ok */ } + try { unlinkSync(keyPath); } catch { /* ok */ } + try { rmdirSync(tempDir); } catch { /* ok */ } + } +} + +/** + * Get last N opinions for a tenant (metadata only, no PDF). + */ +export async function getOpiniones(pool: Pool, limit = 5, rfc?: string): Promise { + const params: unknown[] = [limit]; + let rfcFilter = ''; + if (rfc) { + rfcFilter = 'WHERE rfc = $2'; + params.push(rfc); + } + const { rows } = await pool.query(` + SELECT id, rfc, razon_social as "razonSocial", estatus, folio, + cadena_original as "cadenaOriginal", + fecha_consulta as "fechaConsulta", created_at as "createdAt" + FROM opiniones_cumplimiento + ${rfcFilter} + ORDER BY fecha_consulta DESC + LIMIT $1 + `, params); + return rows; +} + +/** + * Get PDF binary for a specific opinion. + */ +export async function getOpinionPdf(pool: Pool, id: number): Promise { + const { rows } = await pool.query( + `SELECT pdf FROM opiniones_cumplimiento WHERE id = $1`, + [id] + ); + return rows.length > 0 ? rows[0].pdf : null; +} + +/** + * Delete opinions older than 6 months. + */ +export async function limpiarOpinionesAntiguas(pool: Pool): Promise { + const { rowCount } = await pool.query( + `DELETE FROM opiniones_cumplimiento WHERE fecha_consulta < NOW() - interval '6 months'` + ); + return rowCount ?? 0; +} + +/** + * Downloads and stores the Opinión de Cumplimiento for a specific contribuyente + * (despacho mode). Uses FIEL stored in the tenant BD instead of the central BD. + */ +export async function consultarOpinionContribuyente( + pool: Pool, + contribuyenteId: string, +): Promise { + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + const fiel = await getDecryptedFielContribuyente(pool, safeId); + if (!fiel) { + throw new Error('No hay FIEL configurada para este contribuyente o está vencida'); + } + + const tempId = randomUUID(); + const tempDir = join(tmpdir(), `horux-fiel-${tempId}`); + mkdirSync(tempDir, { recursive: true, mode: 0o700 }); + + const cerPath = join(tempDir, 'cert.cer'); + const keyPath = join(tempDir, 'key.key'); + + try { + writeFileSync(cerPath, Buffer.from(fiel.cerContent, 'binary'), { mode: 0o600 }); + writeFileSync(keyPath, Buffer.from(fiel.keyContent, 'binary'), { mode: 0o600 }); + + const browser = await chromium.launch({ headless: true }); + + try { + const context = await browser.newContext({ viewport: { width: 1280, height: 720 } }); + const page = await context.newPage(); + + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout: proceso de opinión excedió 3 minutos')), PROCESS_TIMEOUT) + ); + + const resultPromise = (async () => { + const reportPage = await loginToSatOpinion(page, cerPath, keyPath, fiel.password); + const pdfBuffer = await extractOpinionPdf(reportPage); + const parsed = await parseOpinionPdf(pdfBuffer); + + const { rows } = await pool.query(` + INSERT INTO opiniones_cumplimiento (rfc, razon_social, estatus, folio, cadena_original, fecha_consulta, pdf) + VALUES ($1, $2, $3, $4, $5, NOW(), $6) + RETURNING id, rfc, razon_social as "razonSocial", estatus, folio, cadena_original as "cadenaOriginal", + fecha_consulta as "fechaConsulta", created_at as "createdAt" + `, [parsed.rfc, parsed.razonSocial, parsed.estatus, parsed.folio, parsed.cadenaOriginal, pdfBuffer]); + + return rows[0] as OpinionCumplimiento; + })(); + + return await Promise.race([resultPromise, timeoutPromise]); + } finally { + await browser.close(); + } + } finally { + try { unlinkSync(cerPath); } catch { /* ok */ } + try { unlinkSync(keyPath); } catch { /* ok */ } + try { rmdirSync(tempDir); } catch { /* ok */ } + } +} diff --git a/apps/api/src/services/papeleria.service.ts b/apps/api/src/services/papeleria.service.ts new file mode 100644 index 0000000..fe01184 --- /dev/null +++ b/apps/api/src/services/papeleria.service.ts @@ -0,0 +1,211 @@ +import type { Pool } from 'pg'; + +export const ALLOWED_MIMES = new Set([ + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // docx + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // xlsx +]); + +export const MAX_SIZE_BYTES = 5 * 1024 * 1024; // 5 MB + +export type EstadoPapeleria = 'pendiente' | 'aprobado' | 'rechazado'; + +export interface PapeleriaItem { + id: number; + contribuyenteId: string; + nombre: string; + descripcion: string | null; + archivoFilename: string; + archivoMime: string; + archivoSize: number; + anio: number; + mes: number; + requiereAprobacion: boolean; + estado: EstadoPapeleria | null; + aprobadoPor: string | null; + aprobadoAt: Date | null; + comentarioRechazo: string | null; + subidoPor: string; + createdAt: Date; +} + +const SELECT = ` + id, contribuyente_id, nombre, descripcion, + archivo_filename, archivo_mime, archivo_size, + anio, mes, + requiere_aprobacion, estado, aprobado_por, aprobado_at, comentario_rechazo, + subido_por, created_at +`; + +const ROW = (r: any): PapeleriaItem => ({ + id: r.id, + contribuyenteId: r.contribuyente_id, + nombre: r.nombre, + descripcion: r.descripcion, + archivoFilename: r.archivo_filename, + archivoMime: r.archivo_mime, + archivoSize: r.archivo_size, + anio: r.anio, + mes: r.mes, + requiereAprobacion: r.requiere_aprobacion, + estado: r.estado, + aprobadoPor: r.aprobado_por, + aprobadoAt: r.aprobado_at, + comentarioRechazo: r.comentario_rechazo, + subidoPor: r.subido_por, + createdAt: r.created_at, +}); + +function sanitizeUuid(id: string): string { + return id.replace(/[^a-f0-9-]/gi, ''); +} + +export interface UploadInput { + contribuyenteId: string; + nombre: string; + descripcion: string | null; + anio: number; + mes: number; + requiereAprobacion: boolean; + archivo: Buffer; + archivoFilename: string; + archivoMime: string; + subidoPor: string; +} + +export async function uploadPapeleria( + pool: Pool, + input: UploadInput, +): Promise { + if (!ALLOWED_MIMES.has(input.archivoMime)) { + throw new Error(`Formato no permitido: ${input.archivoMime}. Solo PDF, Word y Excel.`); + } + if (input.archivo.length > MAX_SIZE_BYTES) { + throw new Error(`Archivo excede el máximo de 5 MB (recibido ${(input.archivo.length / 1024 / 1024).toFixed(1)} MB).`); + } + + const estadoInicial = input.requiereAprobacion ? 'pendiente' : null; + + const { rows: [r] } = await pool.query( + `INSERT INTO papeleria_trabajo + (contribuyente_id, nombre, descripcion, archivo, archivo_filename, archivo_mime, archivo_size, + anio, mes, requiere_aprobacion, estado, subido_por) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + RETURNING ${SELECT}`, + [ + sanitizeUuid(input.contribuyenteId), + input.nombre, + input.descripcion, + input.archivo, + input.archivoFilename, + input.archivoMime, + input.archivo.length, + input.anio, + input.mes, + input.requiereAprobacion, + estadoInicial, + input.subidoPor, + ], + ); + return ROW(r); +} + +export interface ListFilters { + contribuyenteId: string; + anio?: number; + mes?: number; + estado?: EstadoPapeleria | 'sin_aprobacion'; +} + +export async function listPapeleria(pool: Pool, f: ListFilters): Promise { + const conds: string[] = ['contribuyente_id = $1']; + const vals: unknown[] = [sanitizeUuid(f.contribuyenteId)]; + let i = 2; + if (f.anio) { conds.push(`anio = $${i++}`); vals.push(f.anio); } + if (f.mes) { conds.push(`mes = $${i++}`); vals.push(f.mes); } + if (f.estado === 'sin_aprobacion') { + conds.push('requiere_aprobacion = false'); + } else if (f.estado) { + conds.push(`estado = $${i++}`); vals.push(f.estado); + } + const { rows } = await pool.query( + `SELECT ${SELECT} FROM papeleria_trabajo + WHERE ${conds.join(' AND ')} + ORDER BY anio DESC, mes DESC, created_at DESC`, + vals, + ); + return rows.map(ROW); +} + +export async function getById(pool: Pool, id: number): Promise { + const { rows: [r] } = await pool.query( + `SELECT ${SELECT} FROM papeleria_trabajo WHERE id = $1`, + [id], + ); + return r ? ROW(r) : null; +} + +export async function downloadArchivo( + pool: Pool, + id: number, +): Promise<{ archivo: Buffer; filename: string; mime: string } | null> { + const { rows: [r] } = await pool.query( + `SELECT archivo, archivo_filename, archivo_mime FROM papeleria_trabajo WHERE id = $1`, + [id], + ); + if (!r) return null; + return { archivo: r.archivo, filename: r.archivo_filename, mime: r.archivo_mime }; +} + +const ROLES_APROBADOR = new Set(['owner', 'cfo', 'supervisor']); + +export async function aprobar( + pool: Pool, + id: number, + userId: string, + userRole: string, +): Promise { + if (!ROLES_APROBADOR.has(userRole)) { + throw new Error('Solo owner o supervisor pueden aprobar papelería'); + } + const { rows: [r] } = await pool.query( + `UPDATE papeleria_trabajo + SET estado = 'aprobado', aprobado_por = $2, aprobado_at = NOW(), + comentario_rechazo = NULL + WHERE id = $1 AND requiere_aprobacion = true + RETURNING ${SELECT}`, + [id, userId], + ); + return r ? ROW(r) : null; +} + +export async function rechazar( + pool: Pool, + id: number, + userId: string, + userRole: string, + comentario: string | null, +): Promise { + if (!ROLES_APROBADOR.has(userRole)) { + throw new Error('Solo owner o supervisor pueden rechazar papelería'); + } + const { rows: [r] } = await pool.query( + `UPDATE papeleria_trabajo + SET estado = 'rechazado', aprobado_por = $2, aprobado_at = NOW(), + comentario_rechazo = $3 + WHERE id = $1 AND requiere_aprobacion = true + RETURNING ${SELECT}`, + [id, userId, comentario], + ); + return r ? ROW(r) : null; +} + +export async function eliminar(pool: Pool, id: number): Promise { + const { rowCount } = await pool.query( + `DELETE FROM papeleria_trabajo WHERE id = $1`, + [id], + ); + return (rowCount ?? 0) > 0; +} diff --git a/apps/api/src/services/payment/addon.service.ts b/apps/api/src/services/payment/addon.service.ts new file mode 100644 index 0000000..10e184b --- /dev/null +++ b/apps/api/src/services/payment/addon.service.ts @@ -0,0 +1,422 @@ +import { prisma } from '../../config/database.js'; +import * as mpService from './mercadopago.service.js'; +import { getTenantOwnerEmail } from '../../utils/memberships.js'; +import { computeEffectiveLimits, type PlanLimits, type AddonDelta } from '../plan-catalogo.service.js'; +import { permiteOverage } from '@horux/shared'; +import { emailService } from '../email/email.service.js'; + +export async function listActiveAddons(tenantId: string, contribuyenteId?: string | null) { + const subscription = await prisma.subscription.findFirst({ + where: { tenantId, status: { in: ['authorized', 'pending', 'trial'] } }, + include: { + addons: { + include: { planAddonCatalogo: true }, + where: { + status: { in: ['authorized', 'pending'] }, + // Si se pide por contribuyente, solo trae los de ese contribuyente. + // Si no, trae todos (tenant-level + todos los contribuyentes). + ...(contribuyenteId !== undefined ? { contribuyenteId } : {}), + }, + }, + }, + }); + + if (!subscription) return { addons: [], subscription: null }; + + return { + subscription: { id: subscription.id, plan: subscription.plan, status: subscription.status }, + addons: subscription.addons.map(a => ({ + id: a.id, + codename: a.planAddonCatalogo.codename, + nombre: a.planAddonCatalogo.nombre, + precio: Number(a.amount), + quantity: a.quantity, + contribuyenteId: a.contribuyenteId, + status: a.status, + currentPeriodStart: a.currentPeriodStart?.toISOString() ?? null, + currentPeriodEnd: a.currentPeriodEnd?.toISOString() ?? null, + })), + }; +} + +export async function subscribeAddon(params: { + tenantId: string; + addonCodename: string; + quantity?: number; + payerEmail?: string; + /** + * UUID del contribuyente (entidad_id en BD tenant) cuando el add-on se ata a + * un RFC específico. Omitido para add-ons tenant-level. + */ + contribuyenteId?: string | null; +}): Promise<{ addon: any; paymentUrl: string }> { + const { tenantId, addonCodename, quantity = 1, contribuyenteId = null } = params; + + const addon = await prisma.planAddonCatalogo.findUnique({ where: { codename: addonCodename } }); + if (!addon || !addon.active) throw new Error('Addon no disponible'); + + const subscription = await prisma.subscription.findFirst({ + where: { tenantId, status: { in: ['authorized', 'trial'] } }, + }); + if (!subscription) throw new Error('No hay suscripción activa'); + + // Un solo addon activo por (subscription, addon, contribuyente?). Para + // tenant-level (contribuyenteId=null) cualquier activo bloquea; para + // per-contribuyente, solo bloquea si ya existe activo para ese mismo RFC. + const existing = await prisma.subscriptionAddon.findFirst({ + where: { + subscriptionId: subscription.id, + planAddonCatalogoId: addon.id, + contribuyenteId: contribuyenteId ?? null, + status: { in: ['authorized', 'pending'] }, + }, + }); + if (existing) throw new Error('Ya tienes este addon activo'); + + const ownerEmail = params.payerEmail || await getTenantOwnerEmail(tenantId); + if (!ownerEmail) throw new Error('No se pudo determinar un email para el cobro'); + + const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { nombre: true } }); + const amount = Number(addon.precio) * quantity; + + // Create the SubscriptionAddon record first so we have an id for external_reference + const subscriptionAddon = await prisma.subscriptionAddon.create({ + data: { + subscriptionId: subscription.id, + planAddonCatalogoId: addon.id, + contribuyenteId: contribuyenteId ?? null, + status: 'pending', + quantity, + amount, + }, + }); + + let mp: { preapprovalId: string; initPoint: string; status: string }; + try { + mp = await mpService.createPreapproval({ + tenantId, + reason: `Horux Despachos - ${addon.nombre}${contribuyenteId ? ` (RFC ${contribuyenteId.slice(0, 8)})` : ''} x${quantity} - ${tenant?.nombre || tenantId}`, + amount, + payerEmail: ownerEmail, + frequency: addon.frecuencia === 'anual' ? 'annual' : 'monthly', + externalReference: `addon:${subscriptionAddon.id}`, + }); + } catch (err) { + // If MP creation fails, clean up the pending record so user can retry + await prisma.subscriptionAddon.delete({ where: { id: subscriptionAddon.id } }); + throw err; + } + + // Update record with the mp preapproval id and final status + const updated = await prisma.subscriptionAddon.update({ + where: { id: subscriptionAddon.id }, + data: { + mpPreapprovalId: mp.preapprovalId, + status: mp.status || 'pending', + }, + }); + + return { addon: updated, paymentUrl: mp.initPoint }; +} + +export async function cancelAddon(tenantId: string, addonId: string): Promise { + const addon = await prisma.subscriptionAddon.findUnique({ + where: { id: addonId }, + include: { subscription: { select: { tenantId: true } } }, + }); + + if (!addon || addon.subscription.tenantId !== tenantId) { + throw new Error('Addon no encontrado'); + } + + if (addon.mpPreapprovalId) { + try { + await mpService.cancelPreapproval(addon.mpPreapprovalId); + } catch (err) { + console.error('[Addon] Error cancelling MP preapproval:', err); + } + } + + await prisma.subscriptionAddon.update({ + where: { id: addonId }, + data: { status: 'cancelled' }, + }); +} + +export async function handleAddonPayment(addonId: string, mpPaymentId: string, status: string): Promise { + const addon = await prisma.subscriptionAddon.findUnique({ + where: { id: addonId }, + include: { subscription: { select: { tenantId: true } } }, + }); + if (!addon) { + console.error(`[Addon Webhook] SubscriptionAddon ${addonId} not found`); + return; + } + + const previousStatus = addon.status; + + if (status === 'authorized' || status === 'approved') { + const now = new Date(); + const periodEnd = new Date(now); + if (addon.amount) { + periodEnd.setMonth(periodEnd.getMonth() + 1); + } + + await prisma.subscriptionAddon.update({ + where: { id: addonId }, + data: { + status: 'authorized', + currentPeriodStart: now, + currentPeriodEnd: periodEnd, + }, + }); + } else if (status === 'cancelled' || status === 'paused' || status === 'rejected') { + await prisma.subscriptionAddon.update({ + where: { id: addonId }, + data: { status }, + }); + + // Aviso fail-soft al owner si el cobro de addon (overage de contribuyentes, + // típicamente $45/mes por extra >100) fue rechazado. Solo en transición + // real — si ya estaba en estado terminal, MP puede re-notificar y no + // queremos spam. + if ( + (status === 'rejected' || status === 'cancelled') && + previousStatus !== status && + addon.subscription + ) { + const tenantId = addon.subscription.tenantId; + const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { nombre: true } }); + const ownerEmail = await getTenantOwnerEmail(tenantId); + if (tenant && ownerEmail) { + emailService.sendPaymentFailed(ownerEmail, { + nombre: tenant.nombre, + amount: Number(addon.amount ?? 0), + plan: 'Addon de contribuyentes adicionales', + }).catch(err => console.error('[EMAIL] addon failed notification:', err)); + } + } + } +} + +// ──────────────────────────────────────────────────────────────── +// Overage automático: Business Control y Enterprise (business_cloud) incluyen +// 100 contribuyentes; cada uno adicional cuesta $45/mes. Se modela como un +// único `SubscriptionAddon` con `codename = 'contribuyente_extra_business_cloud'` +// (codename heredado por compat con suscripciones existentes; nombre display ya +// es genérico), `contribuyenteId = null` (tenant-level) y +// `quantity = activeCount − 100`. El cobro MP usa un preapproval propio; cuando +// `quantity` cambia, se actualiza vía `updatePreapprovalAmount` (sin +// re-autorización del usuario). +// ──────────────────────────────────────────────────────────────── + +const DESPACHO_INCLUDED_RFCS = 100; +const OVERAGE_ADDON_CODENAME = 'contribuyente_extra_business_cloud'; + +export type OverageAction = 'none' | 'created' | 'updated' | 'cancelled' | 'skipped'; + +export interface OverageAdjustResult { + action: OverageAction; + /** Cantidad cobrada tras el ajuste (activeCount − 3, mínimo 0). */ + overageCount: number; + /** Sólo cuando action='created': URL de MercadoPago a presentar al usuario. */ + paymentUrl?: string; + /** Razón si action='skipped' o 'none' (útil para logs/UI). */ + reason?: string; +} + +/** + * Ajusta el add-on de overage para el tenant según el número actual de + * contribuyentes activos. Aplica a planes Business Control y Enterprise + * (business_cloud) — ambos incluyen 100 contribuyentes y cobran $45/mes por + * cada adicional. Idempotente: llamar varias veces con el mismo `activeCount` + * no tiene efecto. + * + * Casos: + * - Plan no permite overage (mi_empresa, mi_empresa_plus, trial, etc.) → 'skipped' + * - Sin suscripción activa → 'skipped' (addon requiere sub) + * - Catálogo no seeded → 'skipped' (error de setup) + * - overage=0 y no hay addon → 'none' + * - overage=0 y hay addon → 'cancelled' (revoca preapproval) + * - overage>0 y no hay addon → 'created' (crea addon + preapproval → paymentUrl) + * - overage>0 y addon.quantity == overage → 'none' (idempotente) + * - overage>0 y addon.quantity distinto → 'updated' (updatePreapprovalAmount) + */ +export async function adjustDespachoOverage( + tenantId: string, + activeContribuyenteCount: number, +): Promise { + const sub = await prisma.subscription.findFirst({ + where: { tenantId, status: { in: ['authorized', 'trial', 'pending'] } }, + orderBy: { createdAt: 'desc' }, + }); + if (!sub) return { action: 'skipped', overageCount: 0, reason: 'Sin suscripción activa' }; + if (!permiteOverage(sub.plan)) { + return { action: 'skipped', overageCount: 0, reason: `Plan ${sub.plan} no aplica overage` }; + } + + const overage = Math.max(0, activeContribuyenteCount - DESPACHO_INCLUDED_RFCS); + + const catalogo = await prisma.planAddonCatalogo.findUnique({ + where: { codename: OVERAGE_ADDON_CODENAME }, + }); + if (!catalogo) { + return { action: 'skipped', overageCount: overage, reason: 'Catálogo overage no seeded' }; + } + + const existing = await prisma.subscriptionAddon.findFirst({ + where: { + subscriptionId: sub.id, + planAddonCatalogoId: catalogo.id, + contribuyenteId: null, + status: { in: ['authorized', 'pending'] }, + }, + }); + + // Bajo o en el límite: cancela el addon si existe + if (overage === 0) { + if (!existing) return { action: 'none', overageCount: 0 }; + if (existing.mpPreapprovalId) { + try { + await mpService.cancelPreapproval(existing.mpPreapprovalId); + } catch (err) { + console.error('[Overage] Error cancelling MP preapproval:', err); + } + } + await prisma.subscriptionAddon.update({ + where: { id: existing.id }, + data: { status: 'cancelled' }, + }); + return { action: 'cancelled', overageCount: 0 }; + } + + // Hay overage → crear o actualizar addon + const price = Number(catalogo.precio); + const newAmount = price * overage; + + if (!existing) { + const ownerEmail = await getTenantOwnerEmail(tenantId); + if (!ownerEmail) { + return { action: 'skipped', overageCount: overage, reason: 'Sin email del owner para cobro' }; + } + const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { nombre: true } }); + + const addon = await prisma.subscriptionAddon.create({ + data: { + subscriptionId: sub.id, + planAddonCatalogoId: catalogo.id, + contribuyenteId: null, + status: 'pending', + quantity: overage, + amount: newAmount, + }, + }); + + let mp: { preapprovalId: string; initPoint: string; status: string }; + try { + mp = await mpService.createPreapproval({ + tenantId, + reason: `Horux Despachos - ${catalogo.nombre} x${overage} - ${tenant?.nombre || tenantId}`, + amount: newAmount, + payerEmail: ownerEmail, + frequency: 'monthly', + externalReference: `addon:${addon.id}`, + }); + } catch (err) { + await prisma.subscriptionAddon.delete({ where: { id: addon.id } }); + throw err; + } + + await prisma.subscriptionAddon.update({ + where: { id: addon.id }, + data: { mpPreapprovalId: mp.preapprovalId, status: mp.status || 'pending' }, + }); + + return { action: 'created', overageCount: overage, paymentUrl: mp.initPoint }; + } + + // Idempotente: si quantity ya coincide, no hay nada que hacer + if (existing.quantity === overage) { + return { action: 'none', overageCount: overage }; + } + + // Actualizar quantity + amount + MP preapproval + await prisma.subscriptionAddon.update({ + where: { id: existing.id }, + data: { quantity: overage, amount: newAmount }, + }); + if (existing.mpPreapprovalId) { + try { + await mpService.updatePreapprovalAmount(existing.mpPreapprovalId, newAmount); + } catch (err) { + console.error('[Overage] Error updating MP amount:', err); + } + } + + return { action: 'updated', overageCount: overage }; +} + +/** + * Cuenta contribuyentes activos del tenant abriendo el pool tenant. Helper + * para callers que viven fuera de un endpoint con `req.tenantPool` (ej. + * `subscription.service.ts` cuando aplica un cambio de plan). + */ +export async function countActiveContribuyentesForTenant(tenantId: string): Promise { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { databaseName: true }, + }); + if (!tenant?.databaseName) return 0; + const { tenantDb } = await import('../../config/database.js'); + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + const { rows: [{ cnt }] } = await pool.query<{ cnt: string }>( + `SELECT COUNT(*)::text AS cnt FROM entidades_gestionadas + WHERE active = true AND tipo = 'CONTRIBUYENTE'`, + ); + return Number(cnt) || 0; +} + +/** + * Cancela el add-on de overage del tenant (si existe) sin importar el status + * actual de la suscripción. Útil cuando una suscripción se cancela y queremos + * cerrar también el preapproval mensual del overage. Idempotente. + */ +export async function cancelOverageAddonForTenant(tenantId: string): Promise<{ cancelled: boolean }> { + const sub = await prisma.subscription.findFirst({ + where: { tenantId }, + orderBy: { createdAt: 'desc' }, + }); + if (!sub) return { cancelled: false }; + + const catalogo = await prisma.planAddonCatalogo.findUnique({ + where: { codename: OVERAGE_ADDON_CODENAME }, + }); + if (!catalogo) return { cancelled: false }; + + const existing = await prisma.subscriptionAddon.findFirst({ + where: { + subscriptionId: sub.id, + planAddonCatalogoId: catalogo.id, + contribuyenteId: null, + status: { in: ['authorized', 'pending'] }, + }, + }); + if (!existing) return { cancelled: false }; + + if (existing.mpPreapprovalId) { + try { + await mpService.cancelPreapproval(existing.mpPreapprovalId); + } catch (err) { + console.error('[Overage] Error cancelling MP preapproval on tenant cancel:', err); + } + } + await prisma.subscriptionAddon.update({ + where: { id: existing.id }, + data: { status: 'cancelled' }, + }); + return { cancelled: true }; +} + +// Re-export types used by callers that need to compose addon deltas with plan limits +export type { PlanLimits, AddonDelta }; +export { computeEffectiveLimits }; diff --git a/apps/api/src/services/payment/invoicing.service.ts b/apps/api/src/services/payment/invoicing.service.ts new file mode 100644 index 0000000..d0fb8bf --- /dev/null +++ b/apps/api/src/services/payment/invoicing.service.ts @@ -0,0 +1,351 @@ +/** + * Auto-facturación de pagos de suscripción. + * + * Cada vez que MercadoPago confirma un pago (webhook `payment.approved`), este + * servicio emite automáticamente un CFDI al público en general vía Facturapi, + * usando la organización de Horux 360 como emisor. + * + * Reglas: + * - El **primer pago** aprobado de cada tenant NO se factura automáticamente — + * el admin lo hace manualmente para verificar/capturar los datos fiscales del + * cliente. Los pagos subsecuentes sí van auto a público en general. + * - Trials (amount=0) no se facturan. + * - Idempotente: si `Payment.facturapiInvoiceId` ya existe, skip. + * - Si Facturapi falla (API down, CSD inválido), se logea el error pero NO se + * tira el webhook — `facturapiInvoiceId` queda null y el admin puede re-emitir + * manualmente después. Esto evita que MP reintente el webhook y que se + * dupliquen registros de Payment. + * + * Emisor: Horux 360 (RFC HTS240708LJA, RESICO PM, régimen 626, sin retenciones). + * Receptor: PUBLICO EN GENERAL (XAXX010101000, régimen 616). + * Concepto: clave prod/serv 81112502 (Servicios de alojamiento de aplicaciones). + */ +import { prisma } from '../../config/database.js'; +import * as facturapiService from '../facturapi.service.js'; +import { GLOBAL_ADMIN_RFC } from '@horux/shared'; +import { auditLog } from '../../utils/audit.js'; +import { getTenantOwnerEmail } from '../../utils/memberships.js'; + +// Constantes de facturación — ajustar aquí si cambia la convención +const CONCEPT_PRODUCT_KEY = '81112502'; // Servicios de alojamiento de aplicaciones +const CONCEPT_UNIT_KEY = 'E48'; // Unidad de servicio +const CONCEPT_UNIT_NAME = 'Servicio'; +// Fallback público en general — se usa cuando el tenant pagador no tiene +// suficientes datos fiscales (sin CSF cargada, sin domicilio, etc.). +const FALLBACK_TAX_ID = 'XAXX010101000'; +const FALLBACK_LEGAL_NAME = 'PUBLICO EN GENERAL'; +const FALLBACK_TAX_SYSTEM = '616'; // Sin obligaciones fiscales +const FALLBACK_USE_CFDI = 'S01'; // Sin efectos fiscales +// Default cuando facturamos con datos reales del cliente — gastos en general. +// Fase 2 hará esto configurable por tenant. +const DEFAULT_USE_CFDI = 'G03'; +const IVA_RATE = 0.16; + +// Mapeo MP payment_method → SAT forma_pago. Conservador: por default TEF (03). +const FORMA_PAGO_POR_METHOD: Record = { + credit_card: '04', // Tarjeta de crédito + debit_card: '28', // Tarjeta de débito + account_money: '03', // Transferencia (MP wallet) + bank_transfer: '03', +}; + +const PLAN_LABELS: Record = { + trial: 'Trial', + custom: 'Custom', + mi_empresa: 'Mi Empresa', + mi_empresa_plus: 'Mi Empresa Plus', + business_control: 'Business Control', + business_cloud: 'Enterprise', +}; + +/** + * Cuenta si este tenant ya tuvo un pago aprobado antes del actual. + * Si no hay ninguno, es el primer pago → devolvemos true (skip auto-emit). + */ +async function isFirstApprovedPayment( + tenantId: string, + excludePaymentId: string, +): Promise { + const count = await prisma.payment.count({ + where: { + tenantId, + status: 'approved', + id: { not: excludePaymentId }, + }, + }); + return count === 0; +} + +/** + * Busca el tenant emisor (Horux 360) con su organización Facturapi configurada. + * Si falta, lanza error — el admin global tiene que crear la organización primero. + */ +async function getEmitterTenant() { + const tenant = await prisma.tenant.findUnique({ + where: { rfc: GLOBAL_ADMIN_RFC }, + select: { + id: true, + nombre: true, + rfc: true, + codigoPostal: true, + facturapiOrgId: true, + }, + }); + if (!tenant) { + throw new Error(`Tenant emisor (RFC ${GLOBAL_ADMIN_RFC}) no existe — corre pnpm bootstrap:admin-global`); + } + if (!tenant.facturapiOrgId) { + throw new Error(`Tenant emisor no tiene organización Facturapi — configúrala en /configuracion`); + } + if (!tenant.codigoPostal) { + throw new Error(`Tenant emisor no tiene código postal — configúralo en /configuracion/domicilio-fiscal`); + } + return tenant; +} + +/** + * Datos fiscales del receptor para la factura. `null` si no hay datos suficientes + * (RFC + razón social + CP + régimen) — el caller cae a público en general. + */ +interface CustomerData { + legalName: string; + taxId: string; + taxSystem: string; + email: string; + zip: string; +} + +/** + * Resuelve los datos fiscales del receptor desde el tenant que paga. + * Requiere CSF sincronizada (régimen) + domicilio fiscal (CP). + * + * Heurística cuando hay múltiples regímenes activos: usa el más antiguo + * (primer regímen agregado al tenant). Fase 2 lo hará configurable. + * + * Retorna `null` si falta cualquier dato requerido — el caller debe caer + * a público en general en ese caso. + */ +async function getCustomerFromTenant(payerTenantId: string): Promise { + const tenant = await prisma.tenant.findUnique({ + where: { id: payerTenantId }, + select: { + nombre: true, + rfc: true, + codigoPostal: true, + factPreferencia: true, + factRegimenPreferido: true, + }, + }); + if (!tenant) return null; + // Si el cliente eligió "público en general" explícitamente, respetar. + if (tenant.factPreferencia === 'publico_general') return null; + if (!tenant.rfc || !tenant.nombre || !tenant.codigoPostal) return null; + + // Régimen fiscal: si el tenant configuró uno preferido, usar ese (validar + // que sigue activo). Si no, heurística "primer activo por createdAt". + let regimenClave: string | null = null; + if (tenant.factRegimenPreferido) { + const activo = await prisma.tenantRegimenActivo.findFirst({ + where: { + tenantId: payerTenantId, + regimen: { clave: tenant.factRegimenPreferido }, + }, + include: { regimen: true }, + }); + if (activo) regimenClave = activo.regimen.clave; + } + if (!regimenClave) { + const regimenActivo = await prisma.tenantRegimenActivo.findFirst({ + where: { tenantId: payerTenantId }, + include: { regimen: true }, + orderBy: { createdAt: 'asc' }, + }); + if (!regimenActivo) return null; + regimenClave = regimenActivo.regimen.clave; + } + + const email = await getTenantOwnerEmail(payerTenantId); + + return { + legalName: tenant.nombre.toUpperCase(), + taxId: tenant.rfc.toUpperCase(), + taxSystem: regimenClave, + email: email || '', + zip: tenant.codigoPostal, + }; +} + +/** + * Construye el payload para Facturapi. Acepta customer real (datos del cliente) + * o fallback a público en general si `customer` es null. + */ +function buildInvoicePayload(params: { + amount: number; + description: string; // Texto del concepto — varía por kind (subscription vs timbres) + emitterCp: string; + paymentMethod: string | null; + customer: CustomerData | null; + usoCfdi: string; // Resuelto por el caller según preferencia del tenant +}) { + const description = params.description; + + // Normaliza método de pago MP → código SAT. Default 03 (TEF) si no mapea. + const normalizedMethod = params.paymentMethod?.toLowerCase().replace(/^proration-/, '') || ''; + const formaPago = FORMA_PAGO_POR_METHOD[normalizedMethod] || '03'; + + const useCustomerData = params.customer !== null; + const customerPayload = useCustomerData + ? { + legalName: params.customer!.legalName, + taxId: params.customer!.taxId, + taxSystem: params.customer!.taxSystem, + email: params.customer!.email, + zip: params.customer!.zip, + } + : { + legalName: FALLBACK_LEGAL_NAME, + taxId: FALLBACK_TAX_ID, + taxSystem: FALLBACK_TAX_SYSTEM, + email: '', + zip: params.emitterCp, + }; + + return { + customer: customerPayload as any, + items: [ + { + description, + productKey: CONCEPT_PRODUCT_KEY, + unitKey: CONCEPT_UNIT_KEY, + unitName: CONCEPT_UNIT_NAME, + quantity: 1, + price: params.amount, // Ya incluye IVA + taxIncluded: true, // Facturapi desagrega subtotal + IVA 16% + taxes: [ + { type: 'IVA', rate: IVA_RATE, factor: 'Tasa' }, + // RESICO PM → sin retenciones + ], + }, + ], + use: params.usoCfdi, + paymentForm: formaPago, + paymentMethod: 'PUE', + currency: 'MXN', + } as facturapiService.FacturapiInvoiceData; +} + +/** + * Entry point. Se llama desde el webhook de MP cuando un pago se confirma. + * Todas las validaciones son fail-soft: loggear y retornar silenciosamente. + */ +export async function emitInvoiceIfApplicable(paymentId: string): Promise { + try { + const payment = await prisma.payment.findUnique({ + where: { id: paymentId }, + include: { subscription: true }, + }); + + if (!payment) { + console.warn(`[Invoicing] Payment ${paymentId} no existe`); + return; + } + + // Gate 1: ya facturado (idempotencia) + if (payment.facturapiInvoiceId) { + console.log(`[Invoicing] Payment ${paymentId} ya facturado (${payment.facturapiInvoiceId}), skip`); + return; + } + + // Gate 2: status + if (payment.status !== 'approved') { + console.log(`[Invoicing] Payment ${paymentId} status=${payment.status}, skip (sólo approved se factura)`); + return; + } + + // Gate 3: amount + const amount = Number(payment.amount); + if (!(amount > 0)) { + console.log(`[Invoicing] Payment ${paymentId} amount=${amount}, skip (trial o cero)`); + return; + } + + // Gate 4: primer pago del tenant → manual + if (await isFirstApprovedPayment(payment.tenantId, payment.id)) { + console.log(`[Invoicing] Payment ${paymentId} es el PRIMER pago aprobado del tenant ${payment.tenantId}, skip (factura manual)`); + return; + } + + // Gate 5: emisor configurado + const emitter = await getEmitterTenant(); + + // Construir payload. El concepto varía por tipo de pago: + // - subscription: "Suscripción {plan} {freq} a Horux 360" + // - timbres_pack: "{cantidad} timbres adicionales — Horux 360" + let description: string; + let auditMetadata: Record; + + if (payment.kind === 'timbres_pack') { + // Recupera cantidad del paquete — vinculado 1:1 con Payment + const paquete = await prisma.timbrePaquete.findUnique({ + where: { paymentId: payment.id }, + }); + const cantidad = paquete?.cantidad ?? 0; + description = `${cantidad.toLocaleString('es-MX')} timbres adicionales — Horux Despachos`; + auditMetadata = { cantidad, amount, kind: 'timbres_pack' }; + } else { + const plan = payment.subscription?.plan || 'trial'; + const frequency = payment.subscription?.frequency || 'monthly'; + const descFrecuencia = frequency === 'annual' ? 'anual' : 'mensual'; + description = `Suscripción ${PLAN_LABELS[plan] || plan} ${descFrecuencia} a Horux Despachos`; + auditMetadata = { amount, plan, frequency, kind: 'subscription' }; + } + + // Resuelve customer real si el tenant pagador tiene CSF + domicilio + + // preferencia 'mis_datos'. Si no, null → buildInvoicePayload cae a público + // en general como fallback seguro. + const customer = await getCustomerFromTenant(payment.tenantId); + if (!customer) { + console.log(`[Invoicing] Tenant ${payment.tenantId} sin datos fiscales completos o preferencia=publico_general. Facturando a Público en General.`); + } + + // Lee uso CFDI preferido del tenant (default G03 ya cargado en BD via default). + const tenantPref = await prisma.tenant.findUnique({ + where: { id: payment.tenantId }, + select: { factUsoCfdi: true }, + }); + const usoCfdi = customer ? (tenantPref?.factUsoCfdi || DEFAULT_USE_CFDI) : FALLBACK_USE_CFDI; + + const payload = buildInvoicePayload({ + amount, + description, + emitterCp: emitter.codigoPostal!, + paymentMethod: payment.paymentMethod, + customer, + usoCfdi, + }); + + console.log(`[Invoicing] Emitiendo factura para Payment ${paymentId} (tenant ${payment.tenantId}, $${amount}, receptor=${customer?.taxId || FALLBACK_TAX_ID})`); + const invoice = await facturapiService.createInvoice(emitter.id, payload); + + await prisma.payment.update({ + where: { id: payment.id }, + data: { facturapiInvoiceId: invoice.id }, + }); + + auditLog({ + tenantId: payment.tenantId, + action: 'invoice.emitted_auto', + entityType: 'Payment', + entityId: payment.id, + metadata: { + facturapiInvoiceId: invoice.id, + ...auditMetadata, + }, + }); + + console.log(`[Invoicing] Factura ${invoice.id} emitida y vinculada a Payment ${paymentId}`); + } catch (error: any) { + // Fail-soft: log y retorno silencioso. El admin puede re-emitir manualmente. + console.error(`[Invoicing] Error emitiendo factura para Payment ${paymentId}:`, error.message || error); + } +} diff --git a/apps/api/src/services/payment/mercadopago.service.ts b/apps/api/src/services/payment/mercadopago.service.ts new file mode 100644 index 0000000..d1dc9b7 --- /dev/null +++ b/apps/api/src/services/payment/mercadopago.service.ts @@ -0,0 +1,340 @@ +import { MercadoPagoConfig, PreApproval, Payment as MPPayment, Preference } from 'mercadopago'; +import { env } from '../../config/env.js'; +import { createHmac } from 'crypto'; + +// Selección del token según MP_USE_SANDBOX. Si se pide sandbox pero no hay +// MP_ACCESS_TOKEN_SANDBOX seteado, cae al de producción con warning — eso permite +// detectar config faltante sin romper el arranque. +const useSandbox = env.MP_USE_SANDBOX && !!env.MP_ACCESS_TOKEN_SANDBOX; +if (env.MP_USE_SANDBOX && !env.MP_ACCESS_TOKEN_SANDBOX) { + console.warn( + '[MP] MP_USE_SANDBOX=true pero MP_ACCESS_TOKEN_SANDBOX no está configurado. ' + + 'Cayendo al token de producción (MP_ACCESS_TOKEN). ' + + 'Configura el TEST-... token en .env para usar sandbox real.' + ); +} else if (useSandbox) { + console.log('[MP] Modo SANDBOX activo — usando MP_ACCESS_TOKEN_SANDBOX para todas las llamadas a MercadoPago.'); +} +const activeToken = useSandbox ? env.MP_ACCESS_TOKEN_SANDBOX! : (env.MP_ACCESS_TOKEN || ''); + +const config = new MercadoPagoConfig({ + accessToken: activeToken, +}); + +const preApprovalClient = new PreApproval(config); +const paymentClient = new MPPayment(config); +const preferenceClient = new Preference(config); + +/** + * Fallback público para `back_url` cuando `FRONTEND_URL` apunta a localhost. + * MercadoPago rechaza URLs `http://localhost...` o cualquier dominio no + * resoluble desde sus servidores con `400 Invalid value for back_url`. + * + * En dev (FRONTEND_URL=http://localhost:3000) sustituimos por la URL de + * producción para que el preapproval/preference se cree exitosamente y MP + * abra su flujo de pago. Después del pago, MP redirige al usuario a esa URL + * de prod (no al local) — para retorno limpio al local hace falta tunnel + * tipo ngrok. Pero el flujo de cobro funciona end-to-end en MP. + * + * En prod (FRONTEND_URL=https://horuxfin.com) es no-op. + */ +const PUBLIC_BACK_URL_FALLBACK = 'https://horuxfin.com'; + +let warnedLocalhost = false; +function backUrlBase(): string { + const fe = env.FRONTEND_URL; + if (!fe || /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)/i.test(fe)) { + if (!warnedLocalhost) { + console.warn( + `[MP] FRONTEND_URL=${fe} no es válida para back_url de MercadoPago. ` + + `Usando fallback público ${PUBLIC_BACK_URL_FALLBACK}. Para retorno ` + + `limpio al local usa ngrok y override FRONTEND_URL.` + ); + warnedLocalhost = true; + } + return PUBLIC_BACK_URL_FALLBACK; + } + return fe; +} + +/** + * Override del payer_email para entornos donde el owner del tenant tiene el + * mismo correo vinculado al MP_ACCESS_TOKEN (vendedor) — MP rechaza con + * "Payer and collector cannot be the same user". En ese caso seteas + * `MP_TEST_PAYER_EMAIL` en `.env` y todas las llamadas a MP usan ese email + * como pagador. Production: dejar sin setear → no-op. + */ +let warnedTestPayer = false; +function resolvePayerEmail(callerEmail: string): string { + if (env.MP_TEST_PAYER_EMAIL) { + if (!warnedTestPayer) { + console.warn( + `[MP] Override de payer_email activo: usando ${env.MP_TEST_PAYER_EMAIL} ` + + `(MP_TEST_PAYER_EMAIL) en lugar del email del owner del tenant. ` + + `Quitar la variable en producción.` + ); + warnedTestPayer = true; + } + return env.MP_TEST_PAYER_EMAIL; + } + return callerEmail; +} + +/** + * Creates a recurring subscription (preapproval) in MercadoPago. + * Soporta cadencia mensual (cada 1 mes) o anual (cada 12 meses). + */ +export async function createPreapproval(params: { + tenantId: string; + reason: string; + amount: number; + payerEmail: string; + frequency?: 'monthly' | 'annual'; + /** + * Fecha del primer cobro. Si no se especifica, MP cobra al día siguiente. + * Útil para reactivaciones: el cliente ya pagó hasta `currentPeriodEnd`, + * queremos que MP empiece a cobrar desde ese momento, no mañana. + */ + startDate?: Date; + /** + * Referencia externa para el preapproval. Si no se especifica, se usa tenantId. + * Usar `addon:{subscriptionAddonId}` para preapprovals de addons. + */ + externalReference?: string; +}) { + if (!env.MP_ACCESS_TOKEN) { + throw new Error( + 'MercadoPago no está configurado (falta MP_ACCESS_TOKEN en .env). ' + + 'Pide al dueño de la cuenta que agregue el token de acceso para habilitar cobros.' + ); + } + + const freq = params.frequency === 'annual' + ? { frequency: 12, frequency_type: 'months' as const } + : { frequency: 1, frequency_type: 'months' as const }; + + // start_date sólo se envía si es en el futuro (MP rechaza fechas pasadas). + const now = new Date(); + const startDateIso = params.startDate && params.startDate.getTime() > now.getTime() + ? params.startDate.toISOString() + : undefined; + + const response = await preApprovalClient.create({ + body: { + reason: params.reason, + external_reference: params.externalReference || params.tenantId, + payer_email: resolvePayerEmail(params.payerEmail), + auto_recurring: { + ...freq, + transaction_amount: params.amount, + currency_id: 'MXN', + ...(startDateIso ? { start_date: startDateIso } : {}), + }, + back_url: `${backUrlBase()}/configuracion/suscripcion`, + }, + }); + + return { + preapprovalId: response.id!, + initPoint: response.init_point!, + status: response.status!, + }; +} + +/** + * Cancela un preapproval en MercadoPago. No falla si ya está cancelado o no existe. + */ +export async function cancelPreapproval(preapprovalId: string): Promise { + try { + await preApprovalClient.update({ + id: preapprovalId, + body: { status: 'cancelled' }, + }); + } catch (error: any) { + // No tiramos el error si el preapproval ya no existe o ya está cancelado + console.warn(`[MP] cancelPreapproval(${preapprovalId}):`, error.message || error); + } +} + +/** + * Actualiza el monto recurrente de un preapproval existente (usado en upgrades: + * después de cobrar el prorateo vía Preference, subimos el monto del preapproval + * para que el próximo cobro recurrente sea el del plan nuevo). + */ +export async function updatePreapprovalAmount( + preapprovalId: string, + newAmount: number, +): Promise { + if (!env.MP_ACCESS_TOKEN) { + throw new Error('MercadoPago no está configurado (falta MP_ACCESS_TOKEN)'); + } + await preApprovalClient.update({ + id: preapprovalId, + body: { + auto_recurring: { + transaction_amount: newAmount, + currency_id: 'MXN', + }, + }, + }); +} + +/** + * Crea una Preference (checkout de pago único) para cobrar el prorateo de un upgrade. + * `externalReference` se prefija con `proration:` para que el webhook distinga este + * pago del cobro recurrente del preapproval. + */ +export async function createProrationPreference(params: { + tenantId: string; + subscriptionId: string; + amount: number; + description: string; + payerEmail: string; +}): Promise<{ preferenceId: string; checkoutUrl: string }> { + if (!env.MP_ACCESS_TOKEN) { + throw new Error( + 'MercadoPago no está configurado (falta MP_ACCESS_TOKEN en .env). ' + + 'No es posible cobrar el prorateo del upgrade.' + ); + } + + const response = await preferenceClient.create({ + body: { + items: [ + { + id: `proration-${params.subscriptionId}`, + title: params.description, + quantity: 1, + unit_price: params.amount, + currency_id: 'MXN', + }, + ], + payer: { email: resolvePayerEmail(params.payerEmail) }, + // El prefijo proration: es el marcador que el webhook usa para ramificar + external_reference: `proration:${params.tenantId}:${params.subscriptionId}`, + back_urls: { + success: `${backUrlBase()}/configuracion/suscripcion?upgrade=success`, + failure: `${backUrlBase()}/configuracion/suscripcion?upgrade=failure`, + pending: `${backUrlBase()}/configuracion/suscripcion?upgrade=pending`, + }, + auto_return: 'approved', + }, + }); + + return { + preferenceId: response.id!, + checkoutUrl: response.init_point!, + }; +} + +/** + * Crea una Preference (checkout de pago único) para comprar un paquete de + * timbres adicionales. external_reference = `timbres-pack:${paymentId}` para + * que el webhook ramifique al handler correspondiente (crea TimbrePaquete + + * marca Payment approved + emite factura). + */ +export async function createTimbrePackPreference(params: { + paymentId: string; // Payment.id del record pre-creado con status=pending + tenantId: string; + cantidad: number; + amount: number; + payerEmail: string; +}): Promise<{ preferenceId: string; checkoutUrl: string }> { + if (!env.MP_ACCESS_TOKEN) { + throw new Error('MercadoPago no está configurado (MP_ACCESS_TOKEN faltante).'); + } + + const title = `${params.cantidad.toLocaleString('es-MX')} timbres adicionales — Horux 360`; + + const response = await preferenceClient.create({ + body: { + items: [ + { + id: `timbres-pack-${params.paymentId}`, + title, + quantity: 1, + unit_price: params.amount, + currency_id: 'MXN', + }, + ], + payer: { email: resolvePayerEmail(params.payerEmail) }, + external_reference: `timbres-pack:${params.paymentId}`, + back_urls: { + success: `${backUrlBase()}/facturacion?timbres=success`, + failure: `${backUrlBase()}/facturacion?timbres=failure`, + pending: `${backUrlBase()}/facturacion?timbres=pending`, + }, + auto_return: 'approved', + }, + }); + + return { + preferenceId: response.id!, + checkoutUrl: response.init_point!, + }; +} + +/** + * Gets subscription (preapproval) status from MercadoPago + */ +export async function getPreapproval(preapprovalId: string) { + const response = await preApprovalClient.get({ id: preapprovalId }); + return { + id: response.id, + status: response.status, + payerEmail: response.payer_email, + nextPaymentDate: response.next_payment_date, + autoRecurring: response.auto_recurring, + }; +} + +/** + * Gets payment details from MercadoPago + */ +export async function getPaymentDetails(paymentId: string) { + const response = await paymentClient.get({ id: paymentId }); + return { + id: response.id, + status: response.status, + statusDetail: response.status_detail, + transactionAmount: response.transaction_amount, + currencyId: response.currency_id, + payerEmail: response.payer?.email, + dateApproved: response.date_approved, + paymentMethodId: response.payment_method_id, + externalReference: response.external_reference, + }; +} + +/** + * Verifies MercadoPago webhook signature (HMAC-SHA256) + */ +export function verifyWebhookSignature( + xSignature: string, + xRequestId: string, + dataId: string +): boolean { + if (!env.MP_WEBHOOK_SECRET) { + console.error('[WEBHOOK] MP_WEBHOOK_SECRET not configured - rejecting webhook'); + return false; + } + + // Parse x-signature header: "ts=...,v1=..." + const parts: Record = {}; + for (const part of xSignature.split(',')) { + const [key, value] = part.split('='); + parts[key.trim()] = value.trim(); + } + + const ts = parts['ts']; + const v1 = parts['v1']; + if (!ts || !v1) return false; + + // Build the manifest string + const manifest = `id:${dataId};request-id:${xRequestId};ts:${ts};`; + const hmac = createHmac('sha256', env.MP_WEBHOOK_SECRET) + .update(manifest) + .digest('hex'); + + return hmac === v1; +} diff --git a/apps/api/src/services/payment/subscription.service.ts b/apps/api/src/services/payment/subscription.service.ts new file mode 100644 index 0000000..f3c1865 --- /dev/null +++ b/apps/api/src/services/payment/subscription.service.ts @@ -0,0 +1,1199 @@ +import { prisma } from '../../config/database.js'; +import * as mpService from './mercadopago.service.js'; +import { emailService } from '../email/email.service.js'; +import { auditLog } from '../../utils/audit.js'; +import { getTenantOwnerEmail } from '../../utils/memberships.js'; +import { isDespachoPaidPlan, permiteOverage, type DespachoPricePhase } from '@horux/shared'; +import { despachoPlanTieneDualidadDb, getPrecioDespachoDb } from '../plan-catalogo.service.js'; +import { + adjustDespachoOverage, + countActiveContribuyentesForTenant, + cancelOverageAddonForTenant, +} from './addon.service.js'; + +// Simple in-memory cache with TTL +const subscriptionCache = new Map(); + +export function invalidateSubscriptionCache(tenantId: string) { + subscriptionCache.delete(`sub:${tenantId}`); +} + +/** + * Creates a subscription record in DB and a MercadoPago preapproval + */ +export async function createSubscription(params: { + tenantId: string; + plan: string; + amount: number; + payerEmail: string; +}) { + const tenant = await prisma.tenant.findUnique({ + where: { id: params.tenantId }, + }); + if (!tenant) throw new Error('Tenant no encontrado'); + + // Create MercadoPago preapproval + const mp = await mpService.createPreapproval({ + tenantId: params.tenantId, + reason: `Horux360 - Plan ${params.plan} - ${tenant.nombre}`, + amount: params.amount, + payerEmail: params.payerEmail, + }); + + // Create DB record + const subscription = await prisma.subscription.create({ + data: { + tenantId: params.tenantId, + plan: params.plan as any, + status: mp.status || 'pending', + amount: params.amount, + frequency: 'monthly', + mpPreapprovalId: mp.preapprovalId, + }, + }); + + invalidateSubscriptionCache(params.tenantId); + + return { + subscription, + paymentUrl: mp.initPoint, + }; +} + +/** + * Gets active subscription for a tenant (cached 5 min) + */ +export async function getActiveSubscription(tenantId: string) { + const cached = subscriptionCache.get(`sub:${tenantId}`); + if (cached && cached.expires > Date.now()) return cached.data; + + const subscription = await prisma.subscription.findFirst({ + where: { tenantId }, + orderBy: { createdAt: 'desc' }, + }); + + subscriptionCache.set(`sub:${tenantId}`, { + data: subscription, + expires: Date.now() + 5 * 60 * 1000, + }); + + return subscription; +} + +/** + * Updates subscription status from webhook notification + */ +export async function updateSubscriptionStatus(mpPreapprovalId: string, status: string) { + const subscription = await prisma.subscription.findFirst({ + where: { mpPreapprovalId }, + }); + if (!subscription) return null; + + const updated = await prisma.subscription.update({ + where: { id: subscription.id }, + data: { status }, + }); + + invalidateSubscriptionCache(subscription.tenantId); + + // Handle cancellation + if (status === 'cancelled') { + const tenant = await prisma.tenant.findUnique({ + where: { id: subscription.tenantId }, + select: { nombre: true }, + }); + const ownerEmail = await getTenantOwnerEmail(subscription.tenantId); + if (tenant && ownerEmail) { + emailService.sendSubscriptionCancelled(ownerEmail, { + nombre: tenant.nombre, + plan: subscription.plan, + }).catch(err => console.error('[EMAIL] Subscription cancelled notification failed:', err)); + } + } + + return updated; +} + +/** + * Records a payment from MercadoPago webhook. Idempotente por `mpPaymentId`: + * MP puede mandar el mismo webhook múltiples veces y solo emitimos un email + * cuando hay transición de estado (no en cada notificación duplicada). + */ +export async function recordPayment(params: { + tenantId: string; + subscriptionId: string; + mpPaymentId: string; + amount: number; + status: string; + paymentMethod: string; +}) { + // Detectar duplicados antes de insertar — `mpPaymentId` no es UNIQUE en el + // schema (puede haber colisiones si MP reusa IDs entre flujos), pero combinado + // con `tenantId` sí es único en la práctica. + const existing = await prisma.payment.findFirst({ + where: { tenantId: params.tenantId, mpPaymentId: params.mpPaymentId }, + }); + + const previousStatus = existing?.status ?? null; + const statusChanged = previousStatus !== params.status; + + let payment; + if (existing) { + payment = await prisma.payment.update({ + where: { id: existing.id }, + data: { + amount: params.amount, + status: params.status, + paymentMethod: params.paymentMethod, + ...(params.status === 'approved' && !existing.paidAt ? { paidAt: new Date() } : {}), + }, + }); + } else { + payment = await prisma.payment.create({ + data: { + tenantId: params.tenantId, + subscriptionId: params.subscriptionId, + mpPaymentId: params.mpPaymentId, + amount: params.amount, + status: params.status, + paymentMethod: params.paymentMethod, + ...(params.status === 'approved' ? { paidAt: new Date() } : {}), + }, + }); + } + + // Solo notificar cuando hay transición real de estado. + if (!statusChanged) return payment; + + const tenant = await prisma.tenant.findUnique({ + where: { id: params.tenantId }, + select: { nombre: true }, + }); + const ownerEmail = await getTenantOwnerEmail(params.tenantId); + + if (tenant && ownerEmail) { + const subscription = await prisma.subscription.findUnique({ + where: { id: params.subscriptionId }, + }); + + if (params.status === 'approved') { + emailService.sendPaymentConfirmed(ownerEmail, { + nombre: tenant.nombre, + amount: params.amount, + plan: subscription?.plan || 'N/A', + date: new Date().toLocaleDateString('es-MX'), + }).catch(err => console.error('[EMAIL] Payment confirmed notification failed:', err)); + } else if (params.status === 'rejected' || params.status === 'cancelled') { + // Tanto `rejected` (banco/MP rechazó) como `cancelled` (user/sistema canceló + // antes de cobro) ameritan aviso al owner — el efecto operativo es el mismo: + // la suscripción no avanzó. + emailService.sendPaymentFailed(ownerEmail, { + nombre: tenant.nombre, + amount: params.amount, + plan: subscription?.plan || 'N/A', + }).catch(err => console.error('[EMAIL] Payment failed notification failed:', err)); + } + } + + return payment; +} + +/** + * Manually marks a subscription as paid (for bank transfers) + */ +export async function markAsPaidManually(tenantId: string, amount: number) { + const subscription = await getActiveSubscription(tenantId); + if (!subscription) throw new Error('No hay suscripción activa'); + + // Update subscription status + await prisma.subscription.update({ + where: { id: subscription.id }, + data: { status: 'authorized' }, + }); + + // Record the manual payment + const payment = await prisma.payment.create({ + data: { + tenantId, + subscriptionId: subscription.id, + mpPaymentId: `manual-${Date.now()}`, + amount, + status: 'approved', + paymentMethod: 'bank_transfer', + }, + }); + + invalidateSubscriptionCache(tenantId); + auditLog({ + tenantId, + action: 'payment.marked_paid_manually', + entityType: 'Payment', + entityId: payment.id, + metadata: { amount, subscriptionId: subscription.id }, + }); + return payment; +} + +/** + * Generates a payment link for a tenant + */ +export async function generatePaymentLink(tenantId: string) { + const tenant = await prisma.tenant.findUnique({ where: { id: tenantId } }); + if (!tenant) throw new Error('Tenant no encontrado'); + const ownerEmail = await getTenantOwnerEmail(tenantId); + if (!ownerEmail) throw new Error('No admin user found'); + + const subscription = await getActiveSubscription(tenantId); + const plan = subscription?.plan || tenant.plan; + const amount = subscription?.amount || 0; + + if (!amount) throw new Error('No se encontró monto de suscripción'); + + const mp = await mpService.createPreapproval({ + tenantId, + reason: `Horux360 - Plan ${plan} - ${tenant.nombre}`, + amount, + payerEmail: ownerEmail, + }); + + // Update subscription with new MP preapproval ID + if (subscription) { + await prisma.subscription.update({ + where: { id: subscription.id }, + data: { mpPreapprovalId: mp.preapprovalId }, + }); + } + + return { paymentUrl: mp.initPoint }; +} + +/** + * Gets payment history for a tenant + */ +export async function getPaymentHistory(tenantId: string) { + return prisma.payment.findMany({ + where: { tenantId }, + orderBy: { createdAt: 'desc' }, + take: 50, + }); +} + +// ============================================================================ +// Self-serve lifecycle (trial, subscribe, change, cancel) +// ============================================================================ + +type Plan = 'trial' | 'custom' | 'business_control' | 'business_cloud' + | 'mi_empresa' | 'mi_empresa_plus'; +type Frequency = 'monthly' | 'annual'; + +/** + * Precio vigente para un (plan, frequency, phase). Lee de BD vía + * `despacho_plan_prices` con cache 5min — admin global edita desde + * `/configuracion/precios-suscripcion` y los nuevos precios aplican + * inmediatamente (cache invalidation post-edit). + * + * - Mi Empresa / Mi Empresa+: aceptan `monthly` o `annual` (anual = 10 + * meses, descuento ~17%). + * - Business Control / Enterprise: solo `annual` (falla si monthly). + * - `custom`: no tiene precio en catálogo, el admin lo fija por tenant. + */ +export async function getPlanPrice( + plan: Plan, + frequency: Frequency, + phase: DespachoPricePhase = 'renewal', +): Promise { + if (plan === 'custom') { + throw new Error('El plan custom no tiene precio en plan_prices — usa createTenant con amount explícito'); + } + return getPrecioDespachoDb(plan, frequency, phase); +} + +/** + * Activa prueba gratuita de 30 días. Una sola vez **por RFC** y, si se pasa + * `ownerUserId`, también una sola vez **por humano** — un mismo dueño no puede + * obtener trials nuevos creando RFCs adicionales. + * + * Gates: + * 1. Plan no puede ser custom + * 2. Tenant no puede tener ya un `trialEndsAt` (su propia prueba en curso o consumida) + * 3. RFC normalizado NO debe existir en `trial_usages` + * 4. Si `ownerUserId`: ningún otro tenant donde el user es owner debe tener + * `trialEndsAt` (cada humano tiene derecho a 1 sola prueba) + * 5. No hay otra suscripción activa/pendiente/trial para este tenant + * + * Inserta el RFC en `trial_usages` dentro de la transacción — si algo falla, rollback + * deja el padrón sin la marca (consistente con la no-creación del trial). + */ +export async function startTrial(params: { + tenantId: string; + plan: Plan; + frequency: Frequency; + ownerUserId?: string; +}): Promise<{ subscription: any; trialEndsAt: Date }> { + if (params.plan === 'custom') { + throw new Error('No se puede iniciar trial en plan custom'); + } + + const tenant = await prisma.tenant.findUnique({ where: { id: params.tenantId } }); + if (!tenant) throw new Error('Tenant no encontrado'); + if (tenant.trialEndsAt) throw new Error('Este tenant ya usó su prueba gratuita'); + + // Gate persistente: RFC ya consumió trial en algún tenant (actual o previo) + const normalizedRfc = tenant.rfc.toUpperCase(); + const priorUsage = await prisma.trialUsage.findUnique({ + where: { rfc: normalizedRfc }, + }); + if (priorUsage) { + throw new Error( + `El RFC ${normalizedRfc} ya consumió su prueba gratuita. ` + + `Cada RFC tiene derecho a una sola prueba de 30 días. Contrata un plan para continuar.` + ); + } + + // Gate por owner: el mismo humano no puede usar trial dos veces creando RFCs + // distintos. Cubre el escenario "borro tenant, creo otro, pido trial otra vez" + // y "agrego segundo RFC bajo mi cuenta y pido trial". + if (params.ownerUserId) { + const ownedTenantWithTrial = await prisma.tenantMembership.findFirst({ + where: { + userId: params.ownerUserId, + isOwner: true, + active: true, + tenantId: { not: params.tenantId }, + tenant: { trialEndsAt: { not: null } }, + }, + select: { tenant: { select: { rfc: true } } }, + }); + if (ownedTenantWithTrial) { + throw new Error( + `Ya consumiste una prueba gratuita con otro RFC (${ownedTenantWithTrial.tenant.rfc}). ` + + `Cada dueño tiene derecho a una sola prueba de 30 días. Para esta empresa contrata un plan directamente.` + ); + } + } + + const existing = await prisma.subscription.findFirst({ + where: { tenantId: params.tenantId, status: { in: ['trial', 'pending', 'authorized', 'paused'] } }, + }); + if (existing) throw new Error('Ya existe una suscripción activa o pendiente'); + + const trialEndsAt = new Date(); + trialEndsAt.setDate(trialEndsAt.getDate() + 30); + + const now = new Date(); + const subscription = await prisma.$transaction(async (tx) => { + await tx.tenant.update({ + where: { id: params.tenantId }, + data: { trialEndsAt, plan: params.plan }, + }); + // Registra el RFC en el padrón — unique constraint previene race condition + await tx.trialUsage.create({ + data: { + rfc: normalizedRfc, + tenantId: params.tenantId, + }, + }); + return tx.subscription.create({ + data: { + tenantId: params.tenantId, + plan: params.plan, + status: 'trial', + amount: 0, + frequency: params.frequency, + currentPeriodStart: now, + currentPeriodEnd: trialEndsAt, + }, + }); + }); + + invalidateSubscriptionCache(params.tenantId); + auditLog({ + tenantId: params.tenantId, + action: 'trial.started', + entityType: 'Subscription', + entityId: subscription.id, + metadata: { plan: params.plan, frequency: params.frequency, rfc: normalizedRfc, trialEndsAt: trialEndsAt.toISOString() }, + }); + console.log(`[Trial] Iniciado para tenant ${params.tenantId} (RFC ${normalizedRfc}), vence ${trialEndsAt.toISOString()}`); + return { subscription, trialEndsAt }; +} + +/** + * Crea una suscripción self-serve (el usuario eligió plan + frecuencia). + * Lee precio de `plan_prices`, crea preapproval en MP, retorna paymentUrl. + * + * Falla si ya hay suscripción activa/pendiente o trial no vencido. Para cambiar + * de plan durante una suscripción activa, usa `scheduleChange`. + */ +export async function subscribe(params: { + tenantId: string; + plan: Plan; + frequency: Frequency; + payerEmail: string; +}): Promise<{ subscription: any; paymentUrl: string }> { + if (params.plan === 'custom') { + throw new Error('Plan custom no es self-serve — lo activa el admin global al crear tenant'); + } + + const tenant = await prisma.tenant.findUnique({ where: { id: params.tenantId } }); + if (!tenant) throw new Error('Tenant no encontrado'); + + const existing = await prisma.subscription.findFirst({ + where: { + tenantId: params.tenantId, + status: { in: ['authorized', 'pending', 'paused'] }, + }, + }); + if (existing) { + throw new Error('Ya existe una suscripción activa o pendiente — usa "Cambiar plan" para modificar'); + } + + // En planes despacho con dualidad (business_control: $21K primer año, $15K + // renovaciones) creamos el preapproval con `firstYear`. Tras el primer pago + // aprobado, el webhook llama `updatePreapprovalAmount` para bajar al monto + // de renewal en los siguientes cobros. El `reason` explica ambos montos al + // usuario en la pantalla de autorización MP. + const amount = await getPlanPrice(params.plan, params.frequency, 'firstYear'); + const hasDualidad = isDespachoPaidPlan(params.plan) && await despachoPlanTieneDualidadDb(params.plan); + const renewalAmount = hasDualidad + ? await getPlanPrice(params.plan, params.frequency, 'renewal') + : amount; + const reason = hasDualidad + ? `${tenant.nombre} - Plan ${params.plan} - $${amount.toLocaleString('es-MX')} primer año, $${renewalAmount.toLocaleString('es-MX')} renovaciones` + : `Horux360 - Plan ${params.plan} (${params.frequency}) - ${tenant.nombre}`; + + const mp = await mpService.createPreapproval({ + tenantId: params.tenantId, + reason, + amount, + payerEmail: params.payerEmail, + frequency: params.frequency, + }); + + // Si había un trial activo, lo marca como completed (no cancelled: el trial terminó exitosamente) + await prisma.subscription.updateMany({ + where: { tenantId: params.tenantId, status: 'trial' }, + data: { status: 'trial_converted' }, + }); + + const subscription = await prisma.subscription.create({ + data: { + tenantId: params.tenantId, + plan: params.plan, + status: mp.status || 'pending', + amount, + frequency: params.frequency, + mpPreapprovalId: mp.preapprovalId, + }, + }); + + await prisma.tenant.update({ + where: { id: params.tenantId }, + data: { plan: params.plan }, + }); + + invalidateSubscriptionCache(params.tenantId); + auditLog({ + tenantId: params.tenantId, + action: 'subscription.created', + entityType: 'Subscription', + entityId: subscription.id, + metadata: { plan: params.plan, frequency: params.frequency, amount }, + }); + return { subscription, paymentUrl: mp.initPoint }; +} + +/** + * Calcula el monto a cobrar por un upgrade prorateado. + * + * proration = (newAmount - currentAmount) * (daysRemaining / periodDays) + * + * Redondeado a 2 decimales. Si no hay días restantes (período vencido), retorna 0 + * — el caller debe caer en scheduleChange en vez de upgrade inmediato. + */ +export function calculateProration( + currentAmount: number, + newAmount: number, + periodStart: Date | null, + periodEnd: Date | null, +): { amount: number; daysRemaining: number; periodDays: number } { + if (!periodStart || !periodEnd) return { amount: 0, daysRemaining: 0, periodDays: 0 }; + const now = Date.now(); + const endMs = periodEnd.getTime(); + if (endMs <= now) return { amount: 0, daysRemaining: 0, periodDays: 0 }; + + const msPerDay = 1000 * 60 * 60 * 24; + const daysRemaining = Math.max(0, Math.ceil((endMs - now) / msPerDay)); + const periodDays = Math.max(1, Math.ceil((endMs - periodStart.getTime()) / msPerDay)); + const fraction = Math.min(1, daysRemaining / periodDays); + const diff = Math.max(0, newAmount - currentAmount); + const amount = Math.round(diff * fraction * 100) / 100; + return { amount, daysRemaining, periodDays }; +} + +/** + * Inicia un upgrade con cobro prorateado inmediato. + * + * Flujo: + * 1. Valida que sea estrictamente un upgrade (precio nuevo > precio actual, misma frecuencia) + * 2. Calcula el prorateo por días restantes del período actual + * 3. Crea una Preference de MercadoPago para ese monto one-time + * 4. Guarda en Subscription: upgradePreferenceId + upgradeTargetPlan + upgradeTargetAmount + * 5. Retorna checkoutUrl — el cliente lo abre en nueva pestaña, el usuario paga + * 6. El webhook detecta `external_reference: proration:*` y llama `applyApprovedUpgrade` + * + * Si falla antes de crear la preference, no hay estado que revertir. Si falla después + * del MP call pero antes del DB update, la preference queda huérfana en MP (expirará sola). + */ +export async function initiateUpgrade(params: { + tenantId: string; + newPlan: Plan; + payerEmail: string; +}): Promise<{ subscription: any; checkoutUrl: string; proratedAmount: number }> { + if (params.newPlan === 'custom') { + throw new Error('No se puede upgrade a plan custom — lo asigna el admin global'); + } + + const active = await prisma.subscription.findFirst({ + where: { + tenantId: params.tenantId, + status: { in: ['authorized', 'trial'] }, + }, + orderBy: { createdAt: 'desc' }, + }); + if (!active) throw new Error('No hay suscripción activa para upgrade'); + if (active.upgradePreferenceId) { + throw new Error('Ya hay un upgrade en curso — cancélalo antes de iniciar otro'); + } + + const currentFrequency = (active.frequency as Frequency) || 'monthly'; + const newAmount = await getPlanPrice(params.newPlan, currentFrequency); + const currentAmount = Number(active.amount); + + if (newAmount <= currentAmount) { + throw new Error('El plan seleccionado no es un upgrade (precio menor o igual). Usa scheduleChange para downgrades.'); + } + + const { amount: proratedAmount, daysRemaining } = calculateProration( + currentAmount, + newAmount, + active.currentPeriodStart, + active.currentPeriodEnd, + ); + if (proratedAmount <= 0) { + throw new Error('No hay días restantes del período actual para prorratear — espera a que termine y contrata el nuevo plan'); + } + + const tenant = await prisma.tenant.findUnique({ where: { id: params.tenantId } }); + if (!tenant) throw new Error('Tenant no encontrado'); + + const description = `Upgrade a ${params.newPlan} — prorateo ${daysRemaining} día${daysRemaining !== 1 ? 's' : ''} restante${daysRemaining !== 1 ? 's' : ''} del período actual`; + + const { preferenceId, checkoutUrl } = await mpService.createProrationPreference({ + tenantId: params.tenantId, + subscriptionId: active.id, + amount: proratedAmount, + description, + payerEmail: params.payerEmail, + }); + + const updated = await prisma.subscription.update({ + where: { id: active.id }, + data: { + upgradePreferenceId: preferenceId, + upgradeTargetPlan: params.newPlan, + upgradeTargetAmount: newAmount, + }, + }); + + invalidateSubscriptionCache(params.tenantId); + + console.log(`[Upgrade] Iniciado para tenant ${params.tenantId}: ${active.plan}→${params.newPlan} (${currentFrequency}). Prorateo: $${proratedAmount}. Preference: ${preferenceId}`); + + return { subscription: updated, checkoutUrl, proratedAmount }; +} + +/** + * Aplica un upgrade cuyo cobro prorateado fue aprobado por MercadoPago (llamado desde webhook). + * + * Acciones: + * 1. Actualiza el monto recurrente del preapproval existente al nuevo precio + * 2. Actualiza Subscription: plan, amount, status=authorized, limpia campos de upgrade + * 3. Actualiza Tenant.plan + * + * Si el paso 1 falla (MP API down), re-lanza para que el webhook no consuma el evento — + * MP reintentará. El paso 2 y 3 deben ser atómicos vía transacción. + */ +export async function applyApprovedUpgrade(subscriptionId: string): Promise { + const sub = await prisma.subscription.findUnique({ + where: { id: subscriptionId }, + }); + if (!sub) throw new Error(`Subscription ${subscriptionId} no encontrada`); + if (!sub.upgradeTargetPlan || !sub.upgradeTargetAmount) { + console.warn(`[Upgrade] Sub ${subscriptionId} sin campos upgradeTarget* — probable webhook duplicado o race, ignorando`); + return; + } + + const newPlan = sub.upgradeTargetPlan as Plan; + const newAmount = Number(sub.upgradeTargetAmount); + + // Actualiza el monto del preapproval en MP (si existe) + if (sub.mpPreapprovalId) { + try { + await mpService.updatePreapprovalAmount(sub.mpPreapprovalId, newAmount); + } catch (error: any) { + console.error(`[Upgrade] Error actualizando preapproval ${sub.mpPreapprovalId}:`, error.message); + throw error; // Re-lanza para que MP reintente el webhook + } + } + + await prisma.$transaction([ + prisma.subscription.update({ + where: { id: sub.id }, + data: { + plan: newPlan, + amount: newAmount, + status: 'authorized', + upgradePreferenceId: null, + upgradeTargetPlan: null, + upgradeTargetAmount: null, + }, + }), + prisma.tenant.update({ + where: { id: sub.tenantId }, + data: { plan: newPlan }, + }), + ]); + + invalidateSubscriptionCache(sub.tenantId); + auditLog({ + tenantId: sub.tenantId, + action: 'subscription.plan_changed', + entityType: 'Subscription', + entityId: sub.id, + metadata: { + kind: 'upgrade_immediate', + fromPlan: sub.plan, + toPlan: newPlan, + frequency: sub.frequency, + newAmount, + }, + }); + console.log(`[Upgrade] Aplicado exitosamente para tenant ${sub.tenantId}: ${sub.plan}→${newPlan} ($${newAmount}/${sub.frequency})`); + + // Reajusta el overage por contribuyente extra al nuevo plan: si pasa de + // un plan sin overage (mi_empresa, etc.) a uno con overage (business_*), + // crea el addon. Si pasa al revés, lo cancela. Fail-soft. + await reconcileOverageAfterPlanChange(sub.tenantId, sub.plan, newPlan); +} + +/** + * Después de cualquier cambio de plan (upgrade, scheduled change aplicado, + * cancelación), ajusta el add-on de overage según corresponda al nuevo plan. + * Fail-soft: cualquier error se logea sin propagar. + */ +async function reconcileOverageAfterPlanChange( + tenantId: string, + fromPlan: string, + toPlan: string, +): Promise { + try { + if (permiteOverage(toPlan)) { + const count = await countActiveContribuyentesForTenant(tenantId); + const result = await adjustDespachoOverage(tenantId, count); + if (result.action !== 'none' && result.action !== 'skipped') { + console.log(`[Overage] Reconcile ${fromPlan}→${toPlan} (tenant ${tenantId}): ${result.action} (count=${result.overageCount})`); + } + } else { + // Plan nuevo NO permite overage → cancelar addon si existía. + const r = await cancelOverageAddonForTenant(tenantId); + if (r.cancelled) { + console.log(`[Overage] Cancelado tras cambio a plan ${toPlan} sin overage (tenant ${tenantId})`); + } + } + } catch (err: any) { + console.error(`[Overage] Reconcile ${fromPlan}→${toPlan} (tenant ${tenantId}) fallo:`, err.message || err); + } +} + +/** + * Aborta un upgrade en curso (el usuario cambió de opinión antes de pagar la preference). + * Simplemente limpia los campos — MP dejará expirar la preference sola. + */ +export async function cancelPendingUpgrade(tenantId: string): Promise { + const sub = await prisma.subscription.findFirst({ + where: { tenantId, upgradePreferenceId: { not: null } }, + }); + if (!sub) throw new Error('No hay upgrade en curso para este tenant'); + + await prisma.subscription.update({ + where: { id: sub.id }, + data: { + upgradePreferenceId: null, + upgradeTargetPlan: null, + upgradeTargetAmount: null, + }, + }); + invalidateSubscriptionCache(tenantId); + console.log(`[Upgrade] Cancelado pendiente para tenant ${tenantId}`); +} + +/** + * Programa un cambio de plan o frecuencia al próximo período. + * Se aplica por el cron `applyPendingChanges` cuando `pendingEffectiveAt` llega. + * + * Usado para downgrades y cambios de frecuencia. Upgrades con misma frecuencia + * deben ir por `initiateUpgrade` (cobro prorateado inmediato). + */ +export async function scheduleChange(params: { + tenantId: string; + newPlan: Plan; + newFrequency: Frequency; +}): Promise<{ subscription: any; effectiveAt: Date }> { + if (params.newPlan === 'custom') { + throw new Error('No se puede cambiar a plan custom — lo asigna el admin global'); + } + + const active = await prisma.subscription.findFirst({ + where: { + tenantId: params.tenantId, + status: { in: ['authorized', 'trial'] }, + }, + orderBy: { createdAt: 'desc' }, + }); + if (!active) throw new Error('No hay suscripción activa para cambiar'); + + if (active.plan === params.newPlan && active.frequency === params.newFrequency) { + throw new Error('El plan y frecuencia son iguales a los actuales'); + } + + // Valida que el nuevo plan/frecuencia tenga precio + await getPlanPrice(params.newPlan, params.newFrequency); + + // Si no hay currentPeriodEnd (raro — trial en curso u otro caso), + // programa el cambio para mañana como salvaguarda + const effectiveAt = active.currentPeriodEnd || new Date(Date.now() + 24 * 60 * 60 * 1000); + + const updated = await prisma.subscription.update({ + where: { id: active.id }, + data: { + pendingPlan: params.newPlan, + pendingFrequency: params.newFrequency, + pendingEffectiveAt: effectiveAt, + }, + }); + + invalidateSubscriptionCache(params.tenantId); + auditLog({ + tenantId: params.tenantId, + action: 'subscription.plan_changed', + entityType: 'Subscription', + entityId: updated.id, + metadata: { + kind: 'scheduled', + fromPlan: active.plan, + toPlan: params.newPlan, + fromFrequency: active.frequency, + toFrequency: params.newFrequency, + effectiveAt: effectiveAt.toISOString(), + }, + }); + return { subscription: updated, effectiveAt }; +} + +/** + * Reactiva una suscripción cancelada que aún está dentro de su período pagado. + * + * MP preapproval cancelado es terminal — no se puede revivir. Esta función crea + * un preapproval nuevo con los mismos parámetros (plan/amount/frequency) y + * `start_date = currentPeriodEnd` para que el primer cobro caiga al final del + * período ya pagado (evita doble cobro). + * + * Resultado: subscription con status=pending + nuevo mpPreapprovalId. El usuario + * debe abrir paymentUrl y autorizar en MP. Al autorizar, webhook → authorized. + * + * Validaciones: + * - Debe existir una subscription status=cancelled con currentPeriodEnd en el futuro + * - Si el período ya venció, redirige al flujo normal de subscribe (picker) + */ +export async function reactivateSubscription(params: { + tenantId: string; + payerEmail: string; +}): Promise<{ subscription: any; paymentUrl: string }> { + const cancelled = await prisma.subscription.findFirst({ + where: { + tenantId: params.tenantId, + status: 'cancelled', + }, + orderBy: { createdAt: 'desc' }, + }); + if (!cancelled) { + throw new Error('No hay suscripción cancelada para reactivar'); + } + + const now = new Date(); + if (!cancelled.currentPeriodEnd || cancelled.currentPeriodEnd.getTime() <= now.getTime()) { + throw new Error('El período pagado ya venció — contrata un nuevo plan desde el selector'); + } + + if (cancelled.plan === 'custom') { + throw new Error('Reactivación de plan custom requiere coordinación con el admin global'); + } + + const tenant = await prisma.tenant.findUnique({ where: { id: params.tenantId } }); + if (!tenant) throw new Error('Tenant no encontrado'); + + const amount = Number(cancelled.amount); + const frequency = (cancelled.frequency as Frequency) || 'monthly'; + + const mp = await mpService.createPreapproval({ + tenantId: params.tenantId, + reason: `Horux360 - Reactivación Plan ${cancelled.plan} (${frequency}) - ${tenant.nombre}`, + amount, + payerEmail: params.payerEmail, + frequency, + startDate: cancelled.currentPeriodEnd, // Primer cobro al final del período actual + }); + + const updated = await prisma.subscription.update({ + where: { id: cancelled.id }, + data: { + status: 'pending', + mpPreapprovalId: mp.preapprovalId, + // Limpia cualquier upgrade/change pendiente que hubiera antes de cancelar + pendingPlan: null, + pendingFrequency: null, + pendingEffectiveAt: null, + upgradePreferenceId: null, + upgradeTargetPlan: null, + upgradeTargetAmount: null, + }, + }); + + // Asegura que tenant.plan refleje el plan reactivado + await prisma.tenant.update({ + where: { id: params.tenantId }, + data: { plan: cancelled.plan }, + }); + + invalidateSubscriptionCache(params.tenantId); + auditLog({ + tenantId: params.tenantId, + action: 'subscription.reactivated', + entityType: 'Subscription', + entityId: updated.id, + metadata: { plan: cancelled.plan, frequency, nextChargeAt: cancelled.currentPeriodEnd!.toISOString() }, + }); + + console.log(`[Reactivate] Tenant ${params.tenantId}: ${cancelled.plan} (${frequency}), próximo cobro: ${cancelled.currentPeriodEnd.toISOString()}`); + + return { subscription: updated, paymentUrl: mp.initPoint }; +} + +/** + * Cancela la suscripción activa. El acceso continúa hasta `currentPeriodEnd` + * (el middleware `plan-limits` sigue respetando `status in (authorized, cancelled)` + * con periodo vigente — no requiere cambio). + * + * También cancela el preapproval en MercadoPago para que no se siga cobrando. + */ +export async function cancelSubscription(tenantId: string): Promise<{ subscription: any }> { + const active = await prisma.subscription.findFirst({ + where: { + tenantId, + status: { in: ['authorized', 'trial', 'pending', 'paused'] }, + }, + orderBy: { createdAt: 'desc' }, + }); + if (!active) throw new Error('No hay suscripción activa para cancelar'); + + // Cancela el addon de overage primero (antes de marcar la sub como cancelled + // para que el lookup de la sub aún la encuentre activa). Fail-soft. + try { + const r = await cancelOverageAddonForTenant(tenantId); + if (r.cancelled) console.log(`[Overage] Cancelado por cancelación de suscripción (tenant ${tenantId})`); + } catch (err: any) { + console.error(`[Overage] Error cancelando addon en cancelSubscription (tenant ${tenantId}):`, err.message || err); + } + + if (active.mpPreapprovalId) { + await mpService.cancelPreapproval(active.mpPreapprovalId); + } + + const updated = await prisma.subscription.update({ + where: { id: active.id }, + data: { + status: 'cancelled', + pendingPlan: null, + pendingFrequency: null, + pendingEffectiveAt: null, + }, + }); + + invalidateSubscriptionCache(tenantId); + auditLog({ + tenantId, + action: 'subscription.cancelled', + entityType: 'Subscription', + entityId: updated.id, + metadata: { plan: active.plan, currentPeriodEnd: active.currentPeriodEnd?.toISOString() }, + }); + + // Email notificación (non-blocking) + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { nombre: true }, + }); + const ownerEmail = await getTenantOwnerEmail(tenantId); + if (tenant && ownerEmail) { + emailService.sendSubscriptionCancelled(ownerEmail, { + nombre: tenant.nombre, + plan: active.plan, + }).catch(err => console.error('[EMAIL] Cancellation email failed:', err)); + } + + return { subscription: updated }; +} + +// ============================================================================ +// Cron helpers: apply pending changes + expire trials +// ============================================================================ + +/** + * Aplica cambios de plan programados cuyo `pendingEffectiveAt` ya pasó. + * Llamado por cron diario. + * + * Para cada cambio pendiente: + * 1. Cancela el preapproval viejo en MP + * 2. Crea nuevo preapproval con el nuevo plan/frecuencia/monto + * 3. Actualiza la Subscription a `pending` (esperando que el usuario autorice el nuevo preapproval) + * + * Nota: si el usuario no autoriza el nuevo preapproval rápidamente, el middleware + * `plan-limits` lo trata como pending — aún con acceso según período previo. + */ +export async function applyPendingChanges(): Promise<{ applied: number; errors: number }> { + const now = new Date(); + const pending = await prisma.subscription.findMany({ + where: { + pendingEffectiveAt: { lte: now, not: null }, + pendingPlan: { not: null }, + status: { in: ['authorized', 'trial'] }, + }, + include: { tenant: true }, + }); + + let applied = 0; + let errors = 0; + + for (const sub of pending) { + try { + const newPlan = sub.pendingPlan as Plan; + const newFrequency = (sub.pendingFrequency || 'monthly') as Frequency; + const newAmount = await getPlanPrice(newPlan, newFrequency); + + const adminEmail = await getTenantOwnerEmail(sub.tenantId); + if (!adminEmail) { + console.error(`[Pending] Sub ${sub.id} sin admin user — omito`); + errors++; + continue; + } + + // Cancelar preapproval viejo + if (sub.mpPreapprovalId) { + await mpService.cancelPreapproval(sub.mpPreapprovalId); + } + + // Crear preapproval nuevo + const mp = await mpService.createPreapproval({ + tenantId: sub.tenantId, + reason: `Horux360 - Plan ${newPlan} (${newFrequency}) - ${sub.tenant.nombre}`, + amount: newAmount, + payerEmail: adminEmail, + frequency: newFrequency, + }); + + await prisma.$transaction([ + prisma.subscription.update({ + where: { id: sub.id }, + data: { + plan: newPlan, + frequency: newFrequency, + amount: newAmount, + status: 'pending', + mpPreapprovalId: mp.preapprovalId, + pendingPlan: null, + pendingFrequency: null, + pendingEffectiveAt: null, + currentPeriodStart: now, + // currentPeriodEnd se actualizará al recibir el webhook de authorization + }, + }), + prisma.tenant.update({ + where: { id: sub.tenantId }, + data: { plan: newPlan }, + }), + ]); + + invalidateSubscriptionCache(sub.tenantId); + + // Reajusta el overage según el nuevo plan (fail-soft). + await reconcileOverageAfterPlanChange(sub.tenantId, sub.plan, newPlan); + + applied++; + console.log(`[Pending] Aplicado cambio para tenant ${sub.tenantId}: ${sub.plan}→${newPlan} (${newFrequency})`); + } catch (error: any) { + console.error(`[Pending] Error en sub ${sub.id}:`, error.message); + errors++; + } + } + + return { applied, errors }; +} + +/** + * Cron diario de avisos pre-vencimiento. Itera suscripciones cuyo `currentPeriodEnd` + * está dentro de los próximos 7 días (o el día mismo del vencimiento). Por cada una + * envía un email al owner con el bucket apropiado (7d, 3d, 1d, 0d) y guarda el bucket + * en `lastReminderDay` para no duplicar. + * + * Idempotencia: + * - Si el bucket actual es menor o igual al guardado, ya se notificó este bucket → skip. + * - Si el bucket actual es MAYOR que el guardado, el período rolló (renovación) — se + * actualiza `lastReminderDay` al nuevo bucket pero NO se envía email (el período + * nuevo está lejos, no hay nada que avisar). Próximas corridas avisarán al bajar. + * + * Buckets (días restantes): 7 → 3 → 1 → 0 (post-vencimiento, último aviso de cortesía). + * Trial usa template `trialReminder`/`trialExpired`; suscripciones de pago usan + * `subscriptionExpiring`. `cancelled` dentro de período también recibe aviso (es la + * señal final antes de pagar de menos). + */ +export async function sendExpiryReminders(): Promise<{ sent: number; resetOnly: number; skipped: number; errors: number }> { + const now = new Date(); + const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); + const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); + + // Solo avisamos a tenants que estén en alguno de estos estados — pending y + // paused no aplican (no han llegado al primer cobro o están suspendidas por MP). + // trial_expired y trial post-expiry caen en el bucket 0 si están dentro del + // último día — ahí mandamos el aviso final de "tu prueba terminó". + const candidates = await prisma.subscription.findMany({ + where: { + OR: [ + // Activos por vencer + { status: { in: ['authorized', 'trial', 'cancelled'] }, currentPeriodEnd: { lte: sevenDaysFromNow, gte: oneDayAgo } }, + // Trial recién vencido (último aviso 0d) + { status: 'trial_expired', currentPeriodEnd: { gte: oneDayAgo } }, + ], + }, + include: { tenant: { select: { nombre: true, rfc: true } } }, + }); + + let sent = 0; + let resetOnly = 0; + let skipped = 0; + let errors = 0; + + for (const sub of candidates) { + if (!sub.currentPeriodEnd) continue; + + // Calcula el bucket actual de días restantes (al cero más cercano hacia abajo). + const msUntil = sub.currentPeriodEnd.getTime() - now.getTime(); + const daysUntil = Math.ceil(msUntil / (24 * 60 * 60 * 1000)); + let bucket: number | null = null; + if (daysUntil <= 0) bucket = 0; + else if (daysUntil <= 1) bucket = 1; + else if (daysUntil <= 3) bucket = 3; + else if (daysUntil <= 7) bucket = 7; + if (bucket === null) { + skipped++; + continue; + } + + const lastBucket = sub.lastReminderDay; + + // Período renovado (ej. lastBucket=0 y bucket=7) — actualiza el tracker pero no envía. + if (lastBucket !== null && bucket > lastBucket) { + await prisma.subscription.update({ + where: { id: sub.id }, + data: { lastReminderDay: bucket, lastReminderSentAt: now }, + }); + resetOnly++; + continue; + } + + // Mismo o menor — ya se notificó este bucket o más cercano. + if (lastBucket !== null && bucket >= lastBucket) { + skipped++; + continue; + } + + // Hay algo que avisar. + try { + const ownerEmail = await getTenantOwnerEmail(sub.tenantId); + if (!ownerEmail) { + skipped++; + continue; + } + + const isTrialFlow = sub.status === 'trial' || sub.status === 'trial_expired'; + if (isTrialFlow) { + if (bucket === 0) { + await emailService.sendTrialExpired(ownerEmail, { + nombre: sub.tenant.nombre, + despachoNombre: sub.tenant.nombre, + }); + } else { + await emailService.sendTrialReminder(ownerEmail, { + nombre: sub.tenant.nombre, + despachoNombre: sub.tenant.nombre, + diasRestantes: Math.max(0, daysUntil), + wizardCompleto: true, + }); + } + } else { + await emailService.sendSubscriptionExpiring(ownerEmail, { + nombre: sub.tenant.nombre, + plan: sub.plan, + expiresAt: sub.currentPeriodEnd.toLocaleDateString('es-MX', { dateStyle: 'long' }), + }); + } + + await prisma.subscription.update({ + where: { id: sub.id }, + data: { lastReminderDay: bucket, lastReminderSentAt: now }, + }); + sent++; + } catch (err: any) { + console.error(`[ExpiryReminder] Error en sub ${sub.id}:`, err.message || err); + errors++; + } + } + + return { sent, resetOnly, skipped, errors }; +} + +/** + * Transiciona trials vencidos (`Tenant.trialEndsAt` ya pasó) a `pending`. + * El usuario debe agregar método de pago para continuar — el middleware `plan-limits` + * empezará a restringir features. + */ +export async function expireTrials(): Promise<{ expired: number }> { + const now = new Date(); + + const expiredTrials = await prisma.subscription.findMany({ + where: { status: 'trial', currentPeriodEnd: { lt: now } }, + }); + + let expired = 0; + for (const sub of expiredTrials) { + await prisma.subscription.update({ + where: { id: sub.id }, + data: { status: 'trial_expired' }, + }); + invalidateSubscriptionCache(sub.tenantId); + expired++; + console.log(`[Trial] Expiró trial de tenant ${sub.tenantId} (plan ${sub.plan})`); + } + + return { expired }; +} diff --git a/apps/api/src/services/plan-catalogo.service.ts b/apps/api/src/services/plan-catalogo.service.ts new file mode 100644 index 0000000..c927adb --- /dev/null +++ b/apps/api/src/services/plan-catalogo.service.ts @@ -0,0 +1,210 @@ +import { prisma } from '../config/database.js'; + +export interface PlanLimits { + maxRfcs: number; + maxUsers: number; + timbresIncluidosMes: number; + features: string[]; +} + +export interface AddonDelta { + maxRfcs?: number; + maxUsers?: number; + timbresIncluidosMes?: number; + features?: string[]; +} + +export interface DespachoPlanLimits { + plan: string; + nombre: string; + monthly: number | null; + firstYear: number | null; + renewal: number | null; + permiteMonthly: boolean; + maxRfcs: number; + maxUsers: number; + timbresIncluidosMes: number; + dbMode: 'BYO' | 'MANAGED'; + permiteServidorBackup: boolean; + permiteSatIncremental: boolean; +} + +/** Suma deltas de addons activos sobre los limits base de un plan. -1 = ilimitado se preserva. */ +export function computeEffectiveLimits(baseLimits: PlanLimits, addonDeltas: AddonDelta[]): PlanLimits { + const result: PlanLimits = { + maxRfcs: baseLimits.maxRfcs, + maxUsers: baseLimits.maxUsers, + timbresIncluidosMes: baseLimits.timbresIncluidosMes, + features: [...baseLimits.features], + }; + + for (const delta of addonDeltas) { + if (delta.maxRfcs) { + result.maxRfcs = result.maxRfcs === -1 ? -1 : result.maxRfcs + delta.maxRfcs; + } + if (delta.maxUsers) { + result.maxUsers = result.maxUsers === -1 ? -1 : result.maxUsers + delta.maxUsers; + } + if (delta.timbresIncluidosMes) { + result.timbresIncluidosMes += delta.timbresIncluidosMes; + } + if (delta.features) { + for (const f of delta.features) { + if (!result.features.includes(f)) result.features.push(f); + } + } + } + + return result; +} + +// ============================================================================ +// Catálogo despacho — lee de despacho_plan_prices con cache 5min +// ============================================================================ + +const CACHE_TTL_MS = 5 * 60 * 1000; +let cacheData: Map | null = null; +let cacheExpiresAt = 0; + +async function loadCache(): Promise> { + const rows = await prisma.despachoPlanPrice.findMany(); + const map = new Map(); + for (const r of rows) { + map.set(r.plan, { + plan: r.plan, + nombre: r.nombre, + monthly: r.monthly !== null ? Number(r.monthly) : null, + firstYear: r.firstYear !== null ? Number(r.firstYear) : null, + renewal: r.renewal !== null ? Number(r.renewal) : null, + permiteMonthly: r.permiteMonthly, + maxRfcs: r.maxRfcs, + maxUsers: r.maxUsers, + timbresIncluidosMes: r.timbresIncluidosMes, + dbMode: r.dbMode as 'BYO' | 'MANAGED', + permiteServidorBackup: r.permiteServidorBackup, + permiteSatIncremental: r.permiteSatIncremental, + }); + } + cacheData = map; + cacheExpiresAt = Date.now() + CACHE_TTL_MS; + return map; +} + +/** Invalida el cache. Llamar tras editar precios/limits desde admin. */ +export function invalidateDespachoPlanCache(): void { + cacheData = null; + cacheExpiresAt = 0; +} + +/** Lee limits + precios de un plan despacho. Cache 5min. */ +export async function getDespachoPlanLimits(plan: string): Promise { + if (!cacheData || Date.now() > cacheExpiresAt) await loadCache(); + return cacheData!.get(plan) ?? null; +} + +/** Lee todos los planes despacho. Cache 5min. */ +export async function getAllDespachoPlanLimits(): Promise { + if (!cacheData || Date.now() > cacheExpiresAt) await loadCache(); + return Array.from(cacheData!.values()); +} + +/** True si el plan acepta frecuencia mensual. Lee de BD via cache. */ +export async function permiteFrecuenciaMensualDb(plan: string): Promise { + const cfg = await getDespachoPlanLimits(plan); + return cfg?.permiteMonthly ?? false; +} + +/** True si el plan cobra distinto en el primer año vs renovaciones. Lee de BD. */ +export async function despachoPlanTieneDualidadDb(plan: string): Promise { + const cfg = await getDespachoPlanLimits(plan); + if (!cfg || cfg.firstYear === null || cfg.renewal === null) return false; + return cfg.firstYear !== cfg.renewal; +} + +/** + * Resuelve el precio MXN para un (plan, frequency, phase) leyendo de BD. + * Throws si el plan no existe en BD o no permite la frecuencia solicitada. + */ +export async function getPrecioDespachoDb( + plan: string, + frequency: 'monthly' | 'annual', + phase: 'firstYear' | 'renewal' = 'renewal', +): Promise { + const cfg = await getDespachoPlanLimits(plan); + if (!cfg) throw new Error(`Plan ${plan} no encontrado en catálogo BD`); + if (frequency === 'monthly') { + if (!cfg.permiteMonthly || cfg.monthly === null) { + throw new Error(`El plan ${plan} no permite frecuencia mensual`); + } + return cfg.monthly; + } + const price = phase === 'firstYear' ? cfg.firstYear : cfg.renewal; + if (price === null) throw new Error(`El plan ${plan} no tiene precio anual configurado`); + return price; +} + +// ============================================================================ +// Endpoints viejos — backward compat con /plan-catalogo/* routes +// (sin callers frontend conocidos; se mantienen por si algo externo consume) +// ============================================================================ + +export async function listPlans(_verticalProfile?: string) { + const all = await getAllDespachoPlanLimits(); + // Excluir trial y custom del catálogo público (admin-only) + return all + .filter(p => p.plan !== 'trial' && p.plan !== 'custom') + .map(p => ({ + codename: p.plan, + nombre: p.nombre, + verticalProfile: 'CONTABLE' as const, + precioBase: p.firstYear ?? 0, + frecuencia: p.permiteMonthly ? 'mensual' : 'anual', + limits: { + maxRfcs: p.maxRfcs, + maxUsers: p.maxUsers, + timbresIncluidosMes: p.timbresIncluidosMes, + features: [], // features viven en TS (DESPACHO_PLANS); este endpoint no las expone + } as PlanLimits, + })); +} + +export async function getPlanByCodename(codename: string) { + const p = await getDespachoPlanLimits(codename); + if (!p) return null; + return { + codename: p.plan, + nombre: p.nombre, + verticalProfile: 'CONTABLE' as const, + precioBase: p.firstYear ?? 0, + frecuencia: p.permiteMonthly ? 'mensual' : 'anual', + limits: { + maxRfcs: p.maxRfcs, + maxUsers: p.maxUsers, + timbresIncluidosMes: p.timbresIncluidosMes, + features: [], + } as PlanLimits, + }; +} + +export async function listAddons(verticalProfile?: string) { + const where: any = { active: true }; + if (verticalProfile) { + where.OR = [ + { verticalProfile }, + { verticalProfile: null }, + ]; + } + const addons = await prisma.planAddonCatalogo.findMany({ + where, + orderBy: { precio: 'asc' }, + }); + return addons.map(a => ({ + id: a.id, + codename: a.codename, + nombre: a.nombre, + verticalProfile: a.verticalProfile, + precio: Number(a.precio), + frecuencia: a.frecuencia, + delta: a.delta as AddonDelta, + })); +} diff --git a/apps/api/src/services/recordatorios.service.ts b/apps/api/src/services/recordatorios.service.ts new file mode 100644 index 0000000..2d4d80f --- /dev/null +++ b/apps/api/src/services/recordatorios.service.ts @@ -0,0 +1,143 @@ +import type { Pool } from 'pg'; +import type { EventoFiscal, EventoCreate, EventoUpdate } from '@horux/shared'; + +/** + * Obtiene recordatorios visibles para el usuario. + * - Públicos: todos los del tenant + * - Privados: solo los creados por el usuario + */ +export async function getRecordatorios( + pool: Pool, + userId: string, + año: number +): Promise { + const { rows } = await pool.query(` + SELECT id, titulo, descripcion, fecha_limite as "fechaLimite", + notas, completado, privado, creado_por as "creadoPor", + created_at as "createdAt" + FROM recordatorios + WHERE EXTRACT(YEAR FROM fecha_limite) = $1 + AND (privado = false OR creado_por = $2) + ORDER BY fecha_limite + `, [año, userId]); + + return rows.map(r => ({ + id: r.id, + titulo: r.titulo, + descripcion: r.descripcion || '', + tipo: 'custom' as const, + fechaLimite: r.fechaLimite instanceof Date + ? r.fechaLimite.toISOString().split('T')[0] + : String(r.fechaLimite).split('T')[0], + recurrencia: 'unica' as const, + completado: r.completado, + notas: r.notas, + privado: r.privado, + creadoPor: r.creadoPor, + createdAt: r.createdAt?.toISOString(), + })); +} + +export async function createRecordatorio( + pool: Pool, + userId: string, + data: EventoCreate & { privado?: boolean } +): Promise { + const { rows } = await pool.query(` + INSERT INTO recordatorios (titulo, descripcion, fecha_limite, notas, privado, creado_por) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id, titulo, descripcion, fecha_limite as "fechaLimite", + notas, completado, privado, creado_por as "creadoPor", + created_at as "createdAt" + `, [ + data.titulo, + data.descripcion || null, + data.fechaLimite, + data.notas || null, + data.privado ?? false, + userId, + ]); + + const r = rows[0]; + return { + id: r.id, + titulo: r.titulo, + descripcion: r.descripcion || '', + tipo: 'custom', + fechaLimite: r.fechaLimite instanceof Date + ? r.fechaLimite.toISOString().split('T')[0] + : String(r.fechaLimite).split('T')[0], + recurrencia: 'unica', + completado: r.completado, + notas: r.notas, + createdAt: r.createdAt?.toISOString(), + }; +} + +export async function updateRecordatorio( + pool: Pool, + userId: string, + id: number, + data: EventoUpdate & { privado?: boolean } +): Promise { + // Verify ownership or public + const { rows: existing } = await pool.query( + `SELECT id, creado_por FROM recordatorios WHERE id = $1`, + [id] + ); + + if (existing.length === 0) return null; + + const sets: string[] = []; + const params: any[] = []; + let idx = 1; + + if (data.titulo !== undefined) { sets.push(`titulo = $${idx++}`); params.push(data.titulo); } + if (data.descripcion !== undefined) { sets.push(`descripcion = $${idx++}`); params.push(data.descripcion); } + if (data.fechaLimite !== undefined) { sets.push(`fecha_limite = $${idx++}`); params.push(data.fechaLimite); } + if (data.completado !== undefined) { sets.push(`completado = $${idx++}`); params.push(data.completado); } + if (data.notas !== undefined) { sets.push(`notas = $${idx++}`); params.push(data.notas); } + if (data.privado !== undefined) { sets.push(`privado = $${idx++}`); params.push(data.privado); } + + if (sets.length === 0) return null; + + sets.push(`updated_at = NOW()`); + params.push(id); + + const { rows } = await pool.query(` + UPDATE recordatorios SET ${sets.join(', ')} + WHERE id = $${idx} + RETURNING id, titulo, descripcion, fecha_limite as "fechaLimite", + notas, completado, privado, creado_por as "creadoPor", + created_at as "createdAt" + `, params); + + if (rows.length === 0) return null; + + const r = rows[0]; + return { + id: r.id, + titulo: r.titulo, + descripcion: r.descripcion || '', + tipo: 'custom', + fechaLimite: r.fechaLimite instanceof Date + ? r.fechaLimite.toISOString().split('T')[0] + : String(r.fechaLimite).split('T')[0], + recurrencia: 'unica', + completado: r.completado, + notas: r.notas, + createdAt: r.createdAt?.toISOString(), + }; +} + +export async function deleteRecordatorio( + pool: Pool, + userId: string, + id: number +): Promise { + const { rowCount } = await pool.query( + `DELETE FROM recordatorios WHERE id = $1`, + [id] + ); + return (rowCount ?? 0) > 0; +} diff --git a/apps/api/src/services/regimen.service.ts b/apps/api/src/services/regimen.service.ts new file mode 100644 index 0000000..3841401 --- /dev/null +++ b/apps/api/src/services/regimen.service.ts @@ -0,0 +1,92 @@ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; + +export async function getAllRegimenes() { + return prisma.regimen.findMany({ + where: { activo: true }, + orderBy: { clave: 'asc' }, + }); +} + +export async function getRegimenesIgnorados(tenantId: string) { + const rows = await prisma.tenantRegimenIgnorado.findMany({ + where: { tenantId }, + include: { regimen: true }, + orderBy: { regimen: { clave: 'asc' } }, + }); + return rows.map(r => r.regimen); +} + +export async function getRegimenesIgnoradosClaves(tenantId: string): Promise { + const rows = await prisma.tenantRegimenIgnorado.findMany({ + where: { tenantId }, + include: { regimen: { select: { clave: true } } }, + }); + return rows.map(r => r.regimen.clave); +} + +export async function getRegimenesActivos(tenantId: string) { + const rows = await prisma.tenantRegimenActivo.findMany({ + where: { tenantId }, + include: { regimen: true }, + orderBy: { regimen: { clave: 'asc' } }, + }); + return rows.map(r => r.regimen); +} + +export async function getRegimenesActivosClaves(tenantId: string): Promise { + const rows = await prisma.tenantRegimenActivo.findMany({ + where: { tenantId }, + include: { regimen: { select: { clave: true } } }, + }); + return rows.map(r => r.regimen.clave); +} + +/** + * Resuelve las claves de regímenes activos para la alerta de discrepancia. + * Si hay contribuyenteId, lee de contribuyentes.regimen_fiscal (comma-separated). + * Si no, fallback a TenantRegimenActivo (tabla central). + */ +export async function getRegimenesActivosClavesEfectivos( + tenantId: string, + pool: Pool, + contribuyenteId?: string | null, +): Promise { + if (contribuyenteId) { + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + const { rows } = await pool.query( + `SELECT regimen_fiscal FROM contribuyentes WHERE entidad_id = $1`, + [safeId], + ); + if (rows.length > 0 && rows[0].regimen_fiscal) { + return rows[0].regimen_fiscal.split(',').map((c: string) => c.trim()).filter(Boolean); + } + return []; + } + return getRegimenesActivosClaves(tenantId); +} + +export async function setRegimenesActivos(tenantId: string, regimenIds: number[]) { + await prisma.tenantRegimenActivo.deleteMany({ where: { tenantId } }); + + if (regimenIds.length > 0) { + await prisma.tenantRegimenActivo.createMany({ + data: regimenIds.map(regimenId => ({ tenantId, regimenId })), + }); + } + + return getRegimenesActivos(tenantId); +} + +export async function setRegimenesIgnorados(tenantId: string, regimenIds: number[]) { + // Delete all existing and re-insert + await prisma.tenantRegimenIgnorado.deleteMany({ where: { tenantId } }); + + if (regimenIds.length > 0) { + await prisma.tenantRegimenIgnorado.createMany({ + data: regimenIds.map(regimenId => ({ tenantId, regimenId })), + }); + } + + return getRegimenesIgnorados(tenantId); +} diff --git a/apps/api/src/services/reportes.service.ts b/apps/api/src/services/reportes.service.ts new file mode 100644 index 0000000..cd45474 --- /dev/null +++ b/apps/api/src/services/reportes.service.ts @@ -0,0 +1,401 @@ +import type { Pool } from 'pg'; +import type { EstadoResultados, FlujoEfectivo, ComparativoPeriodos, ConcentradoRfc } from '@horux/shared'; +import { calcularIngresosPorRegimen, calcularEgresosPorRegimen } from './dashboard.service.js'; + +/** + * Resuelve condiciones `esEmisor` / `esReceptor` para un contribuyente + * usando su RFC. Reemplaza el par `type = 'X' AND contribuyente_id = Y`. + * Si no hay contribuyente, retorna fallback a `type`. + */ +async function resolveEmisorReceptor( + pool: Pool, + contribuyenteId?: string | null, +): Promise<{ esEmisor: string; esReceptor: string }> { + if (!contribuyenteId) { + return { esEmisor: `type = 'EMITIDO'`, esReceptor: `type = 'RECIBIDO'` }; + } + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + if (!safeId) return { esEmisor: `type = 'EMITIDO'`, esReceptor: `type = 'RECIBIDO'` }; + const { rows } = await pool.query<{ rfc: string | null }>( + `SELECT rfc FROM contribuyentes WHERE entidad_id = $1`, + [safeId], + ); + const rfc = (rows[0]?.rfc || '').replace(/[^A-Z0-9]/gi, '').toUpperCase(); + if (!rfc) { + return { + esEmisor: `type = 'EMITIDO' AND contribuyente_id = '${safeId}'`, + esReceptor: `type = 'RECIBIDO' AND contribuyente_id = '${safeId}'`, + }; + } + return { + esEmisor: `UPPER(rfc_emisor) = '${rfc}'`, + esReceptor: `UPPER(rfc_receptor) = '${rfc}'`, + }; +} + +function sanitizeContribUuid(id?: string | null): string { + return id ? id.replace(/[^a-f0-9-]/gi, '') : ''; +} + +function toNumber(value: unknown): number { + if (value === null || value === undefined) return 0; + if (typeof value === 'number') return value; + if (typeof value === 'bigint') return Number(value); + if (typeof value === 'string') return parseFloat(value) || 0; + if (typeof value === 'object' && value !== null && 'toNumber' in value) { + return (value as { toNumber: () => number }).toNumber(); + } + return Number(value) || 0; +} + +export async function getEstadoResultados( + pool: Pool, + fechaInicio: string, + fechaFin: string, + tenantId: string, + contribuyenteId?: string | null, +): Promise { + // Totales usando la misma lógica del dashboard + const ingresosData = await calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, undefined, contribuyenteId); + const egresosData = await calcularEgresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, undefined, contribuyenteId); + + const totalIngresos = ingresosData.total; + const totalEgresos = egresosData.total; + const utilidadBruta = totalIngresos - totalEgresos; + + // Desglose por régimen como conceptos + const ingresosConceptos = ingresosData.porRegimen.map(r => ({ + concepto: `${r.regimenClave} - ${r.regimenDescripcion}`, + monto: r.monto, + })); + + const egresosConceptos = egresosData.porRegimen.map(r => ({ + concepto: `${r.regimenClave} - ${r.regimenDescripcion}`, + monto: r.monto, + })); + + return { + periodo: { inicio: fechaInicio, fin: fechaFin }, + ingresos: ingresosConceptos, + egresos: egresosConceptos, + totalIngresos, + totalEgresos, + utilidadBruta, + impuestos: 0, + utilidadNeta: utilidadBruta, + }; +} + +export async function getFlujoEfectivo( + pool: Pool, + fechaInicio: string, + fechaFin: string, + contribuyenteId?: string | null, +): Promise { + const VIGENTE = `status NOT IN ('Cancelado', '0')`; + const RANGO = `fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day')`; + const RANGO_PAGO = `fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')`; + const { esEmisor, esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId); + + const { rows: entradasPUE } = await pool.query(` + SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${RANGO} + GROUP BY mes ORDER BY mes + `, [fechaInicio, fechaFin]); + + const { rows: entradasPago } = await pool.query(` + SELECT TO_CHAR(fecha_pago_p, 'YYYY-MM') as mes, COALESCE(SUM(monto_pago_mxn), 0) as total + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${RANGO_PAGO} + GROUP BY mes ORDER BY mes + `, [fechaInicio, fechaFin]); + + const { rows: entradasNC } = await pool.query(` + SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND COALESCE(cfdi_tipo_relacion, '') <> '07' + AND ${VIGENTE} AND ${RANGO} + GROUP BY mes ORDER BY mes + `, [fechaInicio, fechaFin]); + + const { rows: salidasPUE } = await pool.query(` + SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE' + AND ${VIGENTE} AND ${RANGO} + GROUP BY mes ORDER BY mes + `, [fechaInicio, fechaFin]); + + const { rows: salidasPago } = await pool.query(` + SELECT TO_CHAR(fecha_pago_p, 'YYYY-MM') as mes, COALESCE(SUM(monto_pago_mxn), 0) as total + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'P' + AND ${VIGENTE} AND ${RANGO_PAGO} + GROUP BY mes ORDER BY mes + `, [fechaInicio, fechaFin]); + + const { rows: salidasNC } = await pool.query(` + SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE' + AND COALESCE(cfdi_tipo_relacion, '') <> '07' + AND ${VIGENTE} AND ${RANGO} + GROUP BY mes ORDER BY mes + `, [fechaInicio, fechaFin]); + + // Combinar por mes + const mesesSet = new Set(); + [...entradasPUE, ...entradasPago, ...entradasNC, ...salidasPUE, ...salidasPago, ...salidasNC].forEach((r: any) => mesesSet.add(r.mes)); + const mesesOrdenados = Array.from(mesesSet).sort(); + + const get = (rows: any[], mes: string) => toNumber(rows.find((r: any) => r.mes === mes)?.total); + + const entradas = mesesOrdenados.map(mes => ({ + concepto: mes, + monto: get(entradasPUE, mes) + get(entradasPago, mes) - get(entradasNC, mes), + })); + + const salidas = mesesOrdenados.map(mes => ({ + concepto: mes, + monto: get(salidasPUE, mes) + get(salidasPago, mes) - get(salidasNC, mes), + })); + + const totalEntradas = entradas.reduce((s, e) => s + e.monto, 0); + const totalSalidas = salidas.reduce((s, e) => s + e.monto, 0); + + return { + periodo: { inicio: fechaInicio, fin: fechaFin }, + saldoInicial: 0, + entradas, + salidas, + totalEntradas, + totalSalidas, + flujoNeto: totalEntradas - totalSalidas, + saldoFinal: totalEntradas - totalSalidas, + }; +} + +/** + * Calcula entradas/salidas de un año completo mes a mes con la lógica de flujo de efectivo. + */ +async function calcularFlujoPorMes(pool: Pool, año: number, contribuyenteId?: string | null): Promise<{ entradas: number[]; salidas: number[] }> { + const VIGENTE = `status NOT IN ('Cancelado', '0')`; + const fi = `${año}-01-01`; + const ff = `${año}-12-31`; + const RANGO = `fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day')`; + const RANGO_PAGO = `fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')`; + const { esEmisor, esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId); + + const q = async (lado: 'EMITIDO' | 'RECIBIDO', tc: string, campo: string, mp?: string) => { + const mpF = mp ? `AND metodo_pago = '${mp}'` : ''; + const fechaCol = tc === 'P' ? 'fecha_pago_p' : 'fecha_emision'; + const rango = tc === 'P' ? RANGO_PAGO : RANGO; + const noAnticipo = tc === 'E' ? `AND COALESCE(cfdi_tipo_relacion, '') <> '07'` : ''; + const ladoCond = lado === 'EMITIDO' ? esEmisor : esReceptor; + const { rows } = await pool.query(` + SELECT EXTRACT(MONTH FROM ${fechaCol})::int as mes, COALESCE(SUM(${campo}), 0) as total + FROM cfdis + WHERE ${ladoCond} AND tipo_comprobante = '${tc}' ${mpF} ${noAnticipo} AND ${VIGENTE} AND ${rango} + GROUP BY mes + `, [fi, ff]); + const map = new Map(); + for (const r of rows) map.set(r.mes, Number(r.total)); + return map; + }; + + const [ePUE, ePago, eNC, sPUE, sPago, sNC] = await Promise.all([ + q('EMITIDO', 'I', 'total_mxn', 'PUE'), + q('EMITIDO', 'P', 'monto_pago_mxn'), + q('EMITIDO', 'E', 'total_mxn', 'PUE'), + q('RECIBIDO', 'I', 'total_mxn', 'PUE'), + q('RECIBIDO', 'P', 'monto_pago_mxn'), + q('RECIBIDO', 'E', 'total_mxn', 'PUE'), + ]); + + const g = (map: Map, m: number) => map.get(m) || 0; + + const entradas: number[] = []; + const salidas: number[] = []; + + for (let m = 1; m <= 12; m++) { + entradas.push(g(ePUE, m) + g(ePago, m) - g(eNC, m)); + salidas.push(g(sPUE, m) + g(sPago, m) - g(sNC, m)); + } + + return { entradas, salidas }; +} + +export async function getComparativo( + pool: Pool, + año: number, + contribuyenteId?: string | null, +): Promise { + const [actual, anterior] = await Promise.all([ + calcularFlujoPorMes(pool, año, contribuyenteId), + calcularFlujoPorMes(pool, año - 1, contribuyenteId), + ]); + + const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']; + const utilidad = actual.entradas.map((e, i) => e - actual.salidas[i]); + + const totalActualIng = actual.entradas.reduce((a, b) => a + b, 0); + const totalAnteriorIng = anterior.entradas.reduce((a, b) => a + b, 0); + const totalActualEgr = actual.salidas.reduce((a, b) => a + b, 0); + const totalAnteriorEgr = anterior.salidas.reduce((a, b) => a + b, 0); + + return { + periodos: meses, + ingresos: actual.entradas, + egresos: actual.salidas, + utilidad, + variacionIngresos: totalAnteriorIng > 0 ? ((totalActualIng - totalAnteriorIng) / totalAnteriorIng) * 100 : 0, + variacionEgresos: totalAnteriorEgr > 0 ? ((totalActualEgr - totalAnteriorEgr) / totalAnteriorEgr) * 100 : 0, + variacionUtilidad: 0, + }; +} + +export async function getConcentradoRfc( + pool: Pool, + fechaInicio: string, + fechaFin: string, + tipo: 'cliente' | 'proveedor', + contribuyenteId?: string | null, +): Promise { + const { esEmisor, esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId); + + if (tipo === 'cliente') { + const { rows: data } = await pool.query(` + SELECT rfc_receptor as rfc, nombre_receptor as nombre, + 'cliente' as tipo, + SUM(total_mxn) as "totalFacturado", + SUM(iva_traslado_mxn) as "totalIva", + COUNT(*)::int as "cantidadCfdis" + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'I' AND status NOT IN ('Cancelado', '0') + AND fecha_emision BETWEEN $1::date AND $2::date + GROUP BY rfc_receptor, nombre_receptor + ORDER BY "totalFacturado" DESC + `, [fechaInicio, fechaFin]); + return data.map((d: any) => ({ + rfc: d.rfc, + nombre: d.nombre, + tipo: 'cliente' as const, + totalFacturado: toNumber(d.totalFacturado), + totalIva: toNumber(d.totalIva), + cantidadCfdis: d.cantidadCfdis + })); + } else { + const { rows: data } = await pool.query(` + SELECT rfc_emisor as rfc, nombre_emisor as nombre, + 'proveedor' as tipo, + SUM(total_mxn) as "totalFacturado", + SUM(iva_traslado_mxn) as "totalIva", + COUNT(*)::int as "cantidadCfdis" + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND status NOT IN ('Cancelado', '0') + AND fecha_emision BETWEEN $1::date AND $2::date + GROUP BY rfc_emisor, nombre_emisor + ORDER BY "totalFacturado" DESC + `, [fechaInicio, fechaFin]); + return data.map((d: any) => ({ + rfc: d.rfc, + nombre: d.nombre, + tipo: 'proveedor' as const, + totalFacturado: toNumber(d.totalFacturado), + totalIva: toNumber(d.totalIva), + cantidadCfdis: d.cantidadCfdis + })); + } +} + +export interface CuentasPendientes { + cantidadCfdis: number; + saldoTotal: number; + detalle: { rfc: string; nombre: string; cantidad: number; saldo: number }[]; +} + +export async function getCuentasXPagar( + pool: Pool, + fechaInicio: string, + fechaFin: string, + regimen?: string, + contribuyenteId?: string | null, +): Promise { + const regimenFilter = regimen ? `AND regimen_fiscal_receptor = '${regimen}'` : ''; + const { esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId); + + const { rows } = await pool.query(` + SELECT + rfc_emisor as rfc, + nombre_emisor as nombre, + COUNT(*)::int as cantidad, + COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) as saldo + FROM cfdis + WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PPD' + AND status NOT IN ('Cancelado', '0') + AND fecha_emision >= $1::date + AND fecha_emision < ($2::date + interval '1 day') + AND COALESCE(saldo_pendiente_mxn, total_mxn) > 0.01 + ${regimenFilter} + GROUP BY rfc_emisor, nombre_emisor + ORDER BY saldo DESC + `, [fechaInicio, fechaFin]); + + const detalle = rows.map((r: any) => ({ + rfc: r.rfc, + nombre: r.nombre, + cantidad: r.cantidad, + saldo: toNumber(r.saldo), + })); + + return { + cantidadCfdis: detalle.reduce((s, d) => s + d.cantidad, 0), + saldoTotal: detalle.reduce((s, d) => s + d.saldo, 0), + detalle, + }; +} + +export async function getCuentasXCobrar( + pool: Pool, + fechaInicio: string, + fechaFin: string, + regimen?: string, + contribuyenteId?: string | null, +): Promise { + const regimenFilter = regimen ? `AND regimen_fiscal_emisor = '${regimen}'` : ''; + const { esEmisor } = await resolveEmisorReceptor(pool, contribuyenteId); + + const { rows } = await pool.query(` + SELECT + rfc_receptor as rfc, + nombre_receptor as nombre, + COUNT(*)::int as cantidad, + COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) as saldo + FROM cfdis + WHERE ${esEmisor} AND tipo_comprobante = 'I' AND metodo_pago = 'PPD' + AND status NOT IN ('Cancelado', '0') + AND fecha_emision >= $1::date + AND fecha_emision < ($2::date + interval '1 day') + AND COALESCE(saldo_pendiente_mxn, total_mxn) > 0.01 + ${regimenFilter} + GROUP BY rfc_receptor, nombre_receptor + ORDER BY saldo DESC + `, [fechaInicio, fechaFin]); + + const detalle = rows.map((r: any) => ({ + rfc: r.rfc, + nombre: r.nombre, + cantidad: r.cantidad, + saldo: toNumber(r.saldo), + })); + + return { + cantidadCfdis: detalle.reduce((s, d) => s + d.cantidad, 0), + saldoTotal: detalle.reduce((s, d) => s + d.saldo, 0), + detalle, + }; +} diff --git a/apps/api/src/services/sat/sat-auth.service.ts b/apps/api/src/services/sat/sat-auth.service.ts new file mode 100644 index 0000000..b015bf7 --- /dev/null +++ b/apps/api/src/services/sat/sat-auth.service.ts @@ -0,0 +1,160 @@ +import { XMLParser, XMLBuilder } from 'fast-xml-parser'; +import { createHash, randomUUID } from 'crypto'; +import type { Credential } from '@nodecfdi/credentials/node'; + +const SAT_AUTH_URL = 'https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/Autenticacion/Autenticacion.svc'; + +interface SatToken { + token: string; + expiresAt: Date; +} + +/** + * Genera el timestamp para la solicitud SOAP + */ +function createTimestamp(): { created: string; expires: string } { + const now = new Date(); + const created = now.toISOString(); + const expires = new Date(now.getTime() + 5 * 60 * 1000).toISOString(); // 5 minutos + return { created, expires }; +} + +/** + * Construye el XML de solicitud de autenticación + */ +function buildAuthRequest(credential: Credential): string { + const timestamp = createTimestamp(); + const uuid = randomUUID(); + + const certificate = credential.certificate(); + // El PEM ya contiene el certificado en base64, solo quitamos headers y newlines + const cerB64 = certificate.pem().replace(/-----.*-----/g, '').replace(/\s/g, ''); + + // Canonicalizar y firmar + const toDigestXml = `` + + `${timestamp.created}` + + `${timestamp.expires}` + + ``; + + const digestValue = createHash('sha1').update(toDigestXml).digest('base64'); + + const signedInfoXml = `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `${digestValue}` + + `` + + ``; + + // Firmar con la llave privada (sign retorna binary string, convertir a base64) + const signatureBinary = credential.sign(signedInfoXml, 'sha1'); + const signatureValue = Buffer.from(signatureBinary, 'binary').toString('base64'); + + const soapEnvelope = ` + + + + + ${timestamp.created} + ${timestamp.expires} + + ${cerB64} + + ${signedInfoXml} + ${signatureValue} + + + + + + + + + + + +`; + + return soapEnvelope; +} + +/** + * Extrae el token de la respuesta SOAP + */ +function parseAuthResponse(responseXml: string): SatToken { + const parser = new XMLParser({ + ignoreAttributes: false, + removeNSPrefix: true, + }); + + const result = parser.parse(responseXml); + + // Navegar la estructura de respuesta SOAP + const envelope = result.Envelope || result['s:Envelope']; + if (!envelope) { + throw new Error('Respuesta SOAP inválida'); + } + + const body = envelope.Body || envelope['s:Body']; + if (!body) { + throw new Error('No se encontró el cuerpo de la respuesta'); + } + + const autenticaResponse = body.AutenticaResponse; + if (!autenticaResponse) { + throw new Error('No se encontró AutenticaResponse'); + } + + const autenticaResult = autenticaResponse.AutenticaResult; + if (!autenticaResult) { + throw new Error('No se obtuvo token de autenticación'); + } + + // El token es un SAML assertion en base64 + const token = autenticaResult; + + // El token expira en 5 minutos según documentación SAT + const expiresAt = new Date(Date.now() + 5 * 60 * 1000); + + return { token, expiresAt }; +} + +/** + * Autentica con el SAT usando la FIEL y obtiene un token + */ +export async function authenticate(credential: Credential): Promise { + const soapRequest = buildAuthRequest(credential); + + try { + const response = await fetch(SAT_AUTH_URL, { + method: 'POST', + headers: { + 'Content-Type': 'text/xml;charset=UTF-8', + 'SOAPAction': 'http://DescargaMasivaTerceros.gob.mx/IAutenticacion/Autentica', + }, + body: soapRequest, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Error HTTP ${response.status}: ${errorText}`); + } + + const responseXml = await response.text(); + return parseAuthResponse(responseXml); + } catch (error: any) { + console.error('[SAT Auth Error]', error); + throw new Error(`Error al autenticar con el SAT: ${error.message}`); + } +} + +/** + * Verifica si un token está vigente + */ +export function isTokenValid(token: SatToken): boolean { + return new Date() < token.expiresAt; +} diff --git a/apps/api/src/services/sat/sat-client.service.ts b/apps/api/src/services/sat/sat-client.service.ts new file mode 100644 index 0000000..01307b4 --- /dev/null +++ b/apps/api/src/services/sat/sat-client.service.ts @@ -0,0 +1,248 @@ +import { + Fiel, + HttpsWebClient, + FielRequestBuilder, + Service, + QueryParameters, + DateTimePeriod, + DownloadType, + RequestType, + DocumentStatus, + ServiceEndpoints, +} from '@nodecfdi/sat-ws-descarga-masiva'; + +export interface FielData { + cerContent: string; + keyContent: string; + password: string; +} + +/** + * Crea el servicio de descarga masiva del SAT usando los datos de la FIEL + */ +export function createSatService(fielData: FielData): Service { + // Crear FIEL usando el método estático create + const fiel = Fiel.create(fielData.cerContent, fielData.keyContent, fielData.password); + + // Verificar que la FIEL sea válida + if (!fiel.isValid()) { + throw new Error('La FIEL no es válida o está vencida'); + } + + // Crear cliente HTTP + const webClient = new HttpsWebClient(); + + // Crear request builder con la FIEL + const requestBuilder = new FielRequestBuilder(fiel); + + // Crear y retornar el servicio + return new Service(requestBuilder, webClient, undefined, ServiceEndpoints.cfdi()); +} + +export interface QueryResult { + success: boolean; + requestId?: string; + message: string; + statusCode?: string; +} + +export interface VerifyResult { + success: boolean; + status: 'pending' | 'processing' | 'ready' | 'failed' | 'rejected'; + packageIds: string[]; + totalCfdis: number; + message: string; + statusCode?: string; +} + +export interface DownloadResult { + success: boolean; + packageContent: string; // Base64 encoded ZIP + message: string; +} + +/** + * Realiza una consulta al SAT para solicitar CFDIs + */ +export async function querySat( + service: Service, + fechaInicio: Date, + fechaFin: Date, + tipo: 'emitidos' | 'recibidos', + requestType: 'metadata' | 'cfdi' = 'cfdi' +): Promise { + try { + const period = DateTimePeriod.createFromValues( + formatDateForSat(fechaInicio), + formatDateForSat(fechaFin) + ); + + const downloadType = new DownloadType(tipo === 'emitidos' ? 'issued' : 'received'); + const reqType = new RequestType(requestType === 'cfdi' ? 'xml' : 'metadata'); + + // XMLs: solo vigentes (active). Metadata: todos (undefined = sin filtro). + let parameters = QueryParameters.create(period, downloadType, reqType); + if (requestType === 'cfdi') { + parameters = parameters.withDocumentStatus(new DocumentStatus('active')); + } + const result = await service.query(parameters); + + if (!result.getStatus().isAccepted()) { + return { + success: false, + message: result.getStatus().getMessage(), + statusCode: result.getStatus().getCode().toString(), + }; + } + + return { + success: true, + requestId: result.getRequestId(), + message: 'Solicitud aceptada', + statusCode: result.getStatus().getCode().toString(), + }; + } catch (error: any) { + console.error('[SAT Query Error]', error); + return { + success: false, + message: error.message || 'Error al realizar consulta', + }; + } +} + +/** + * Verifica el estado de una solicitud + */ +export async function verifySatRequest( + service: Service, + requestId: string +): Promise { + try { + const result = await service.verify(requestId); + const statusRequest = result.getStatusRequest(); + + // `codeRequest` es el código SAT específico del estado de la solicitud + // (5000 Accepted, 5002 Exhausted, 5003 MaximumLimit, 5004 EmptyResult, + // 5005 Duplicated) y su mensaje explica POR QUÉ el SAT rechazó. Es la + // pieza clave para diagnosticar rejections — el `getStatus().getCode()` + // solo devuelve el wrapper HTTP (5000 genérico "Aceptada"). + // + // Fuente: docs phpcfdi + lib @nodecfdi/sat-ws-descarga-masiva (`CodeRequest`). + const codeReqObj = typeof (result as any).getCodeRequest === 'function' + ? (result as any).getCodeRequest() + : null; + const codeRequestValue = codeReqObj ? codeReqObj.getValue() : null; + const codeRequestMessage = codeReqObj ? codeReqObj.getMessage() : null; + const codeRequestEntry = codeReqObj ? codeReqObj.getEntryId() : null; + + // Debug logging + console.log('[SAT Verify Debug]', { + statusRequestValue: statusRequest.getValue(), + statusRequestEntryId: statusRequest.getEntryId(), + cfdis: result.getNumberCfdis(), + packages: result.getPackageIds(), + statusCode: result.getStatus().getCode(), + statusMsg: result.getStatus().getMessage(), + codeRequestValue, + codeRequestEntry, + codeRequestMessage, + }); + + // Usar isTypeOf para determinar el estado + let status: VerifyResult['status']; + if (statusRequest.isTypeOf('Finished')) { + status = 'ready'; + } else if (statusRequest.isTypeOf('InProgress')) { + status = 'processing'; + } else if (statusRequest.isTypeOf('Accepted')) { + status = 'pending'; + } else if (statusRequest.isTypeOf('Failure')) { + status = 'failed'; + } else if (statusRequest.isTypeOf('Rejected')) { + status = 'rejected'; + } else { + // Default: check by entryId + const entryId = statusRequest.getEntryId(); + if (entryId === 'Finished') status = 'ready'; + else if (entryId === 'InProgress') status = 'processing'; + else if (entryId === 'Accepted') status = 'pending'; + else status = 'pending'; + } + + // Para estados terminales no-felices, construir mensaje informativo. + // `codeRequest` (si está disponible) es la razón SAT real del rechazo. + const statusCode = result.getStatus().getCode().toString(); + const statusMsg = result.getStatus().getMessage(); + const reqValue = statusRequest.getValue(); + const reqEntry = statusRequest.getEntryId(); + let message = statusMsg; + if (status === 'rejected' || status === 'failed') { + const codeReqStr = codeRequestValue + ? ` codeRequest=${codeRequestEntry}(${codeRequestValue}) — ${codeRequestMessage}` + : ''; + message = `SAT request=${reqEntry}(${reqValue})${codeReqStr} wrapperCode=${statusCode} wrapperMsg="${statusMsg}"`; + } + + return { + success: result.getStatus().isAccepted(), + status, + packageIds: result.getPackageIds(), + totalCfdis: result.getNumberCfdis(), + message, + statusCode, + }; + } catch (error: any) { + console.error('[SAT Verify Error]', error.message || error); + // Errores de la librería (ej. webError.getResponse is not a function) + // no son fallos del SAT — devolver 'pending' para reintentar polling + return { + success: false, + status: 'pending', + packageIds: [], + totalCfdis: 0, + message: error.message || 'Error al verificar solicitud', + }; + } +} + +/** + * Descarga un paquete de CFDIs + */ +export async function downloadSatPackage( + service: Service, + packageId: string +): Promise { + try { + const result = await service.download(packageId); + + if (!result.getStatus().isAccepted()) { + return { + success: false, + packageContent: '', + message: result.getStatus().getMessage(), + }; + } + + return { + success: true, + packageContent: result.getPackageContent(), + message: 'Paquete descargado', + }; + } catch (error: any) { + console.error('[SAT Download Error]', error); + return { + success: false, + packageContent: '', + message: error.message || 'Error al descargar paquete', + }; + } +} + +/** + * Formatea una fecha para el SAT (YYYY-MM-DD HH:mm:ss) + */ +function formatDateForSat(date: Date): string { + const pad = (n: number) => n.toString().padStart(2, '0'); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; +} diff --git a/apps/api/src/services/sat/sat-crypto.service.ts b/apps/api/src/services/sat/sat-crypto.service.ts new file mode 100644 index 0000000..0a52ee1 --- /dev/null +++ b/apps/api/src/services/sat/sat-crypto.service.ts @@ -0,0 +1,94 @@ +import { encryptAesGcm, decryptAesGcm, deriveAesKey } from '@horux/core'; +import { env } from '../../config/env.js'; + +function getKey(): Buffer { + return deriveAesKey(env.FIEL_ENCRYPTION_KEY); +} + +/** + * Encripta datos usando AES-256-GCM con la clave derivada de FIEL_ENCRYPTION_KEY + */ +export function encrypt(data: Buffer): { encrypted: Buffer; iv: Buffer; tag: Buffer } { + return encryptAesGcm(data, getKey()); +} + +/** + * Desencripta datos usando AES-256-GCM con la clave derivada de FIEL_ENCRYPTION_KEY + */ +export function decrypt(encrypted: Buffer, iv: Buffer, tag: Buffer): Buffer { + return decryptAesGcm(encrypted, iv, tag, getKey()); +} + +/** + * Encripta un string y retorna los componentes + */ +export function encryptString(text: string): { encrypted: Buffer; iv: Buffer; tag: Buffer } { + return encrypt(Buffer.from(text, 'utf-8')); +} + +/** + * Desencripta a string + */ +export function decryptToString(encrypted: Buffer, iv: Buffer, tag: Buffer): string { + return decrypt(encrypted, iv, tag).toString('utf-8'); +} + +/** + * Encripta credenciales FIEL con IV/tag independiente por componente + */ +export function encryptFielCredentials( + cerData: Buffer, + keyData: Buffer, + password: string +): { + encryptedCer: Buffer; + encryptedKey: Buffer; + encryptedPassword: Buffer; + cerIv: Buffer; + cerTag: Buffer; + keyIv: Buffer; + keyTag: Buffer; + passwordIv: Buffer; + passwordTag: Buffer; +} { + const cer = encrypt(cerData); + const key = encrypt(keyData); + const pwd = encrypt(Buffer.from(password, 'utf-8')); + + return { + encryptedCer: cer.encrypted, + encryptedKey: key.encrypted, + encryptedPassword: pwd.encrypted, + cerIv: cer.iv, + cerTag: cer.tag, + keyIv: key.iv, + keyTag: key.tag, + passwordIv: pwd.iv, + passwordTag: pwd.tag, + }; +} + +/** + * Desencripta credenciales FIEL (per-component IV/tag) + */ +export function decryptFielCredentials( + encryptedCer: Buffer, + encryptedKey: Buffer, + encryptedPassword: Buffer, + cerIv: Buffer, + cerTag: Buffer, + keyIv: Buffer, + keyTag: Buffer, + passwordIv: Buffer, + passwordTag: Buffer +): { + cerData: Buffer; + keyData: Buffer; + password: string; +} { + const cerData = decrypt(encryptedCer, cerIv, cerTag); + const keyData = decrypt(encryptedKey, keyIv, keyTag); + const password = decrypt(encryptedPassword, passwordIv, passwordTag).toString('utf-8'); + + return { cerData, keyData, password }; +} diff --git a/apps/api/src/services/sat/sat-csf-login.ts b/apps/api/src/services/sat/sat-csf-login.ts new file mode 100644 index 0000000..a067877 --- /dev/null +++ b/apps/api/src/services/sat/sat-csf-login.ts @@ -0,0 +1,84 @@ +import type { Browser, BrowserContext, Page } from 'playwright'; + +const PUBLIC_URL = 'https://www.sat.gob.mx/portal/public/tramites/constancia-de-situacion-fiscal'; + +export interface CsfLoginSession { + context: BrowserContext; + appPage: Page; +} + +/** + * Navigates from the public CSF page → "SERVICIO" popup → FIEL login → + * returns the post-login app page (popup that became the SPA). + * Ver referencia_sat_portal_csf: el botón "Generar" vive en un iframe JSF + * dentro de esta appPage, por eso la retornamos tal cual. + */ +export async function loginSatCsf( + browser: Browser, + cerPath: string, + keyPath: string, + password: string, +): Promise { + const context = await browser.newContext({ acceptDownloads: true }); + const publicPage = await context.newPage(); + publicPage.setDefaultTimeout(60_000); + + await publicPage.goto(PUBLIC_URL, { waitUntil: 'networkidle' }); + await publicPage.waitForTimeout(2000); + + // Click acordeón "Obtén tu constancia" / "Obtener constancia" + const obtenerLocator = publicPage.locator( + 'text=/Obt[eé]n\\s+la\\s+constancia|Obt[eé]n\\s+tu\\s+constancia|Obtener\\s+constancia|Obtener\\s+la\\s+constancia/i', + ).first(); + await obtenerLocator.waitFor({ state: 'visible', timeout: 60_000 }); + await obtenerLocator.scrollIntoViewIfNeeded(); + await obtenerLocator.click(); + await publicPage.waitForTimeout(1500); + + // Click "SERVICIO" → popup + const popupPromise = context.waitForEvent('page', { timeout: 60_000 }); + await publicPage.locator('text=/^\\s*SERVICIO\\s*$/i').first().click(); + const loginPage = await popupPromise; + await loginPage.waitForLoadState('domcontentloaded'); + loginPage.setDefaultTimeout(60_000); + + // Click "e.firma" (NO "e.firma portable"). El SAT a veces aterriza en la + // pestaña de contraseña: el botón cambia a la vista FIEL. El click sintético + // de Playwright a veces no dispara el handler — afirmamos el efecto (aparece + // el file input) y reintentamos con dispatchEvent si hace falta. + const efirmaBtn = loginPage + .locator('button:has-text("e.firma"):not(:has-text("portable")), input[type="button"][value="e.firma" i], input[type="submit"][value="e.firma" i]') + .first(); + await efirmaBtn.waitFor({ state: 'visible', timeout: 30_000 }); + await efirmaBtn.scrollIntoViewIfNeeded(); + await efirmaBtn.click(); + + const fileInputs = loginPage.locator('input[type="file"]'); + try { + await fileInputs.first().waitFor({ state: 'attached', timeout: 10_000 }); + } catch { + // Retry: el click sintético no disparó el handler — forzamos dispatchEvent + await efirmaBtn.dispatchEvent('click'); + await fileInputs.first().waitFor({ state: 'attached', timeout: 30_000 }); + } + + // Upload .cer (primer input) y .key (segundo) + await fileInputs.nth(0).setInputFiles(cerPath); + await fileInputs.nth(1).setInputFiles(keyPath); + + // Password + Enviar + await loginPage.locator('input[type="password"]').first().fill(password); + await loginPage.locator('button:has-text("Enviar"), input[value="Enviar"]').first().click(); + + // Esperar a que salga del dominio de login + await loginPage.waitForURL(url => !url.toString().includes('loginda.siat.sat.gob.mx'), { timeout: 60_000 }); + await loginPage.waitForLoadState('networkidle').catch(() => undefined); + await loginPage.waitForTimeout(2000); + + const bodyText = await loginPage.locator('body').innerText().catch(() => ''); + if (/contrase[nñ]a\s+incorrecta|usuario.*no.*v[aá]lido|firma\s+inv[aá]lida/i.test(bodyText)) { + throw new Error('FIEL inválida o contraseña incorrecta'); + } + + return { context, appPage: loginPage }; +} diff --git a/apps/api/src/services/sat/sat-csf-parser.ts b/apps/api/src/services/sat/sat-csf-parser.ts new file mode 100644 index 0000000..03a6cc6 --- /dev/null +++ b/apps/api/src/services/sat/sat-csf-parser.ts @@ -0,0 +1,246 @@ +import { PDFParse } from 'pdf-parse'; + +export interface Domicilio { + codigoPostal?: string; + tipoVialidad?: string; + nombreVialidad?: string; + numeroExterior?: string; + numeroInterior?: string; + colonia?: string; + localidad?: string; + municipio?: string; + entidadFederativa?: string; + entreCalle?: string; + yCalle?: string; +} + +export interface ActividadEconomica { + orden: number; + descripcion: string; + porcentaje: number; + fechaInicio: string; + fechaFin?: string; +} + +export interface RegimenCsf { + nombre: string; + fechaInicio: string; + fechaFin?: string; +} + +export interface Obligacion { + descripcion: string; + descripcionVencimiento: string; + fechaInicio: string; + fechaFin?: string; +} + +export interface ConstanciaSituacionFiscal { + rfc: string; + curp?: string; + idCIF: string; + nombre?: string; + primerApellido?: string; + segundoApellido?: string; + razonSocial?: string; + nombreComercial?: string; + fechaInicioOperaciones: string; + estatusPadron: string; + fechaUltimoCambioEstado?: string; + lugarFechaEmision: string; + domicilio: Domicilio; + actividadesEconomicas: ActividadEconomica[]; + regimenes: RegimenCsf[]; + obligaciones: Obligacion[]; + cadenaOriginalSello: string; + selloDigital: string; +} + +async function extractPdfText(pdfBuffer: Buffer): Promise { + const parser = new PDFParse({ data: pdfBuffer }); + try { + const result = await parser.getText(); + return result.text; + } finally { + await parser.destroy(); + } +} + +const LABELS = [ + 'RFC', 'CURP', 'Nombre (s)', 'Primer Apellido', 'Segundo Apellido', + 'Denominación o Razón Social', 'Denominación/Razón Social', + 'Régimen Capital', 'Fecha inicio de operaciones', 'Estatus en el padrón', + 'Fecha de último cambio de estado', 'Nombre Comercial', + 'Código Postal', 'Tipo de Vialidad', 'Nombre de Vialidad', + 'Número Exterior', 'Número Interior', 'Nombre de la Colonia', + 'Nombre de la Localidad', 'Nombre del Municipio o Demarcación Territorial', + 'Nombre de la Entidad Federativa', 'Entre Calle', 'Y Calle', +] as const; + +function escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function extractLabels(text: string): Map { + const result = new Map(); + const labelAlternation = LABELS.map(escapeRegex).join('|'); + const re = new RegExp( + `(${labelAlternation})\\s*:\\s*([\\s\\S]*?)(?=\\s+(?:${labelAlternation})\\s*:|\\n?\\s*(?:Datos del domicilio registrado|Actividades Económicas|Regímenes|Obligaciones|Cadena Original|Sus datos personales)\\b|\\n\\s*--\\s*\\d+\\s+of\\s+\\d+|$)`, + 'g', + ); + for (const match of text.matchAll(re)) { + const label = match[1]; + const value = match[2].replace(/\s+/g, ' ').trim(); + if (!result.has(label)) result.set(label, value); + } + return result; +} + +function extractIdCIF(text: string): string { + const m = text.match(/idCIF\s*:?\s*(\d+)/i); + if (!m) throw new Error('idCIF no encontrado en PDF'); + return m[1]; +} + +function extractLugarFechaEmision(text: string): string { + const m = text.match(/Lugar y Fecha de Emisión\s*\n?\s*([^\n]+?)\s*(?=\n|TORC|HTS|[A-Z]{4}\d{6})/); + if (m) return m[1].replace(/\s+/g, ' ').trim(); + const m2 = text.match(/([A-ZÁÉÍÓÚÑ ]+,\s*[A-ZÁÉÍÓÚÑ ]+\s+A\s+\d{1,2}\s+DE\s+[A-ZÁÉÍÓÚÑ]+\s+DE\s+\d{4})/i); + if (m2) return m2[1].replace(/\s+/g, ' ').trim(); + throw new Error('Lugar y Fecha de Emisión no encontrado'); +} + +const PAGE_NOISE_RE = /^\s*(?:--\s*\d+\s+of\s+\d+\s*--|Página\s*\[\d+\]\s*de\s*\[\d+\])\s*$/; + +function sliceSection(text: string, header: string, nextHeaders: string[]): string { + const start = text.indexOf(header); + if (start === -1) return ''; + const after = start + header.length; + let end = text.length; + for (const h of nextHeaders) { + const idx = text.indexOf(h, after); + if (idx !== -1 && idx < end) end = idx; + } + return text.slice(after, end); +} + +function groupRowChunks(body: string, headerRowRegex: RegExp): string[] { + const lines = body.split(/\r?\n/).map(l => l.trim()).filter(l => l.length > 0 && !PAGE_NOISE_RE.test(l)); + if (lines.length > 0 && headerRowRegex.test(lines[0])) lines.shift(); + const chunks: string[] = []; + let current: string[] = []; + for (const line of lines) { + current.push(line); + if (/\d{2}\/\d{2}\/\d{4}\s*$/.test(line)) { + chunks.push(current.join(' ').replace(/\s+/g, ' ').trim()); + current = []; + } + } + return chunks; +} + +function extractActividades(text: string): ActividadEconomica[] { + const section = sliceSection(text, 'Actividades Económicas:', ['Regímenes:', 'Obligaciones:', 'Cadena Original']); + if (!section) return []; + const chunks = groupRowChunks(section, /^\s*Orden\s+Actividad\s+Económica\s+Porcentaje\s+Fecha\s+Inicio\s+Fecha\s+Fin\s*$/i); + const result: ActividadEconomica[] = []; + for (const chunk of chunks) { + const m = chunk.match(/^(\d+)\s+(.+?)\s+(\d+)\s+(\d{2}\/\d{2}\/\d{4})(?:\s+(\d{2}\/\d{2}\/\d{4}))?$/); + if (!m) continue; + result.push({ + orden: Number(m[1]), + descripcion: m[2].replace(/\s+/g, ' ').trim(), + porcentaje: Number(m[3]), + fechaInicio: m[4], + fechaFin: m[5], + }); + } + return result; +} + +function extractRegimenes(text: string): RegimenCsf[] { + const section = sliceSection(text, 'Regímenes:', ['Obligaciones:', 'Cadena Original']); + if (!section) return []; + const chunks = groupRowChunks(section, /^\s*Régimen\s+Fecha\s+Inicio\s+Fecha\s+Fin\s*$/i); + const result: RegimenCsf[] = []; + for (const chunk of chunks) { + const m = chunk.match(/^(.+?)\s+(\d{2}\/\d{2}\/\d{4})(?:\s+(\d{2}\/\d{2}\/\d{4}))?$/); + if (!m) continue; + result.push({ nombre: m[1].replace(/\s+/g, ' ').trim(), fechaInicio: m[2], fechaFin: m[3] }); + } + return result; +} + +function extractObligaciones(text: string): Obligacion[] { + const section = sliceSection(text, 'Obligaciones:', ['Sus datos personales', 'Cadena Original']); + if (!section) return []; + const chunks = groupRowChunks(section, /^\s*Descripción de la Obligación\s+Descripción Vencimiento\s+Fecha Inicio\s+Fecha Fin\s*$/i); + const result: Obligacion[] = []; + for (const chunk of chunks) { + const m = chunk.match(/^(.+?)\s+((?:A\s+m[aá]s\s+tardar|Dentro\s+de|Mensualmente|Bimestralmente|Trimestralmente|Anualmente|En\s+los|Cuando\s+)[\s\S]+?)\s+(\d{2}\/\d{2}\/\d{4})(?:\s+(\d{2}\/\d{2}\/\d{4}))?$/); + if (!m) continue; + result.push({ descripcion: m[1].trim(), descripcionVencimiento: m[2].trim(), fechaInicio: m[3], fechaFin: m[4] }); + } + return result; +} + +function extractCadenaOriginalSello(text: string): string { + const m = text.match(/Cadena Original Sello\s*:\s*(\|\|[\s\S]+?\|\|)\s*(?:Sello Digital|$)/); + if (!m) throw new Error('Cadena Original Sello no encontrada'); + return m[1].replace(/\s+/g, ''); +} + +function extractSelloDigital(text: string): string { + const m = text.match(/Sello Digital\s*:\s*([A-Za-z0-9+/=\s]+?)(?:\n\s*\n|Página|$)/); + if (!m) throw new Error('Sello Digital no encontrado'); + return m[1].replace(/\s+/g, ''); +} + +export async function parseCsfPdf(pdfBuffer: Buffer): Promise { + const text = await extractPdfText(pdfBuffer); + const labels = extractLabels(text); + const idCIF = extractIdCIF(text); + const lugarFechaEmision = extractLugarFechaEmision(text); + + const rfc = labels.get('RFC'); + if (!rfc) throw new Error('RFC no encontrado en PDF'); + + const fechaInicioOperaciones = labels.get('Fecha inicio de operaciones'); + if (!fechaInicioOperaciones) throw new Error('Fecha inicio de operaciones no encontrada'); + + const estatusPadron = labels.get('Estatus en el padrón'); + if (!estatusPadron) throw new Error('Estatus en el padrón no encontrado'); + + return { + rfc, + curp: labels.get('CURP'), + idCIF, + nombre: labels.get('Nombre (s)'), + primerApellido: labels.get('Primer Apellido'), + segundoApellido: labels.get('Segundo Apellido'), + razonSocial: labels.get('Denominación o Razón Social') ?? labels.get('Denominación/Razón Social'), + nombreComercial: labels.get('Nombre Comercial') || undefined, + fechaInicioOperaciones, + estatusPadron, + fechaUltimoCambioEstado: labels.get('Fecha de último cambio de estado'), + lugarFechaEmision, + domicilio: { + codigoPostal: labels.get('Código Postal'), + tipoVialidad: labels.get('Tipo de Vialidad'), + nombreVialidad: labels.get('Nombre de Vialidad'), + numeroExterior: labels.get('Número Exterior'), + numeroInterior: labels.get('Número Interior'), + colonia: labels.get('Nombre de la Colonia'), + localidad: labels.get('Nombre de la Localidad'), + municipio: labels.get('Nombre del Municipio o Demarcación Territorial'), + entidadFederativa: labels.get('Nombre de la Entidad Federativa'), + entreCalle: labels.get('Entre Calle'), + yCalle: labels.get('Y Calle'), + }, + actividadesEconomicas: extractActividades(text), + regimenes: extractRegimenes(text), + obligaciones: extractObligaciones(text), + cadenaOriginalSello: extractCadenaOriginalSello(text), + selloDigital: extractSelloDigital(text), + }; +} diff --git a/apps/api/src/services/sat/sat-csf-scraper.ts b/apps/api/src/services/sat/sat-csf-scraper.ts new file mode 100644 index 0000000..37e4b8e --- /dev/null +++ b/apps/api/src/services/sat/sat-csf-scraper.ts @@ -0,0 +1,121 @@ +import type { Page, Locator, Frame, Response } from 'playwright'; +import type { CsfLoginSession } from './sat-csf-login.js'; + +async function tryFetchPdfFromUrl(page: Page, url: string): Promise { + if (url.startsWith('blob:') || url.startsWith('data:')) { + const arr = await page.evaluate(async (u) => { + const r = await fetch(u); + const buf = await r.arrayBuffer(); + return Array.from(new Uint8Array(buf)); + }, url); + return Buffer.from(arr); + } + if (url.startsWith('http')) { + const response = await page.context().request.get(url); + if (!response.ok()) return null; + return Buffer.from(await response.body()); + } + return null; +} + +/** + * Busca "Generar Constancia" en cualquiera de los frames del appPage (vive + * típicamente en un iframe JSF legacy: rfcampc.siat.sat.gob.mx/PTSC/...). + * Intenta 3 rutas: download event, popup con viewer, response interception. + */ +export async function extractCsfPdf(session: CsfLoginSession): Promise { + const { context, appPage } = session; + + let interceptedPdf: Buffer | null = null; + const responseListener = async (response: Response) => { + const ct = response.headers()['content-type'] ?? ''; + if (ct.includes('application/pdf')) { + try { interceptedPdf = Buffer.from(await response.body()); } catch { /* ok */ } + } + }; + context.on('response', responseListener); + + const GENERAR_SELECTORS = [ + 'button:has-text("Generar Constancia")', + 'button:has-text("Generar constancia")', + 'input[type="button"][value*="Generar" i]', + 'input[type="submit"][value*="Generar" i]', + 'a:has-text("Generar Constancia")', + 'a:has-text("Generar constancia")', + ].join(', '); + + let generarLocator: Locator | null = null; + let foundFrame: Frame | null = null; + const deadline = Date.now() + 90_000; + while (Date.now() < deadline) { + for (const frame of appPage.frames()) { + const loc = frame.locator(GENERAR_SELECTORS).first(); + const count = await loc.count().catch(() => 0); + if (count > 0 && await loc.isVisible().catch(() => false)) { + generarLocator = loc; + foundFrame = frame; + break; + } + } + if (generarLocator) break; + await appPage.waitForTimeout(1000); + } + + if (!generarLocator || !foundFrame) { + context.off('response', responseListener); + throw new Error('Botón "Generar Constancia" no encontrado en ningún frame del portal SAT (tras 90s)'); + } + + await generarLocator.scrollIntoViewIfNeeded(); + await appPage.waitForTimeout(500); + + const popupPromise = context.waitForEvent('page', { timeout: 15_000 }).catch(() => null); + const downloadPromise = appPage.waitForEvent('download', { timeout: 15_000 }).catch(() => null); + await generarLocator.click(); + + const [popup, download] = await Promise.all([popupPromise, downloadPromise]); + + try { + // Path 1: download event + if (download) { + const stream = await download.createReadStream(); + const chunks: Buffer[] = []; + for await (const chunk of stream) chunks.push(chunk as Buffer); + const pdf = Buffer.concat(chunks); + if (!pdf.subarray(0, 5).toString().startsWith('%PDF-')) { + throw new Error('El archivo descargado no es un PDF válido'); + } + return pdf; + } + + // Path 2: viewer popup + if (popup) { + await popup.waitForLoadState('domcontentloaded').catch(() => undefined); + await popup.waitForTimeout(2000); + + let pdf = await tryFetchPdfFromUrl(popup, popup.url()).catch(() => null); + if (!pdf) { + const embedSrc = await popup.locator('embed[type="application/pdf"], iframe').first().getAttribute('src').catch(() => null); + if (embedSrc) { + const absolute = new URL(embedSrc, popup.url()).toString(); + pdf = await tryFetchPdfFromUrl(popup, absolute).catch(() => null); + } + } + if (!pdf && interceptedPdf) pdf = interceptedPdf; + if (!pdf || pdf.length === 0) throw new Error('El visor abrió pero no se pudo extraer el PDF'); + if (!pdf.subarray(0, 5).toString().startsWith('%PDF-')) throw new Error('Buffer extraído no es un PDF válido'); + return pdf; + } + + // Path 3: inline response (no popup, no download) + await appPage.waitForTimeout(3000); + if (interceptedPdf) { + const pdf = interceptedPdf as Buffer; + if (!pdf.subarray(0, 5).toString().startsWith('%PDF-')) throw new Error('Buffer interceptado no es un PDF válido'); + return pdf; + } + throw new Error('Click en "Generar Constancia" no produjo descarga, popup ni respuesta PDF'); + } finally { + context.off('response', responseListener); + } +} diff --git a/apps/api/src/services/sat/sat-download.service.ts b/apps/api/src/services/sat/sat-download.service.ts new file mode 100644 index 0000000..4489246 --- /dev/null +++ b/apps/api/src/services/sat/sat-download.service.ts @@ -0,0 +1,408 @@ +import { XMLParser } from 'fast-xml-parser'; +import { createHash, randomUUID } from 'crypto'; +import type { Credential } from '@nodecfdi/credentials/node'; +import type { + SatDownloadRequestResponse, + SatVerifyResponse, + SatPackageResponse, + CfdiSyncType +} from '@horux/shared'; + +const SAT_SOLICITUD_URL = 'https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/SolicitaDescargaService.svc'; +const SAT_VERIFICA_URL = 'https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/VerificaSolicitudDescargaService.svc'; +const SAT_DESCARGA_URL = 'https://cfdidescargamasaborrar.clouda.sat.gob.mx/DescargaMasivaService.svc'; + +type TipoSolicitud = 'CFDI' | 'Metadata'; + +interface RequestDownloadParams { + credential: Credential; + token: string; + rfc: string; + fechaInicio: Date; + fechaFin: Date; + tipoSolicitud: TipoSolicitud; + tipoCfdi: CfdiSyncType; +} + +/** + * Formatea fecha a formato SAT (YYYY-MM-DDTHH:MM:SS) + */ +function formatSatDate(date: Date): string { + return date.toISOString().slice(0, 19); +} + +/** + * Construye el XML de solicitud de descarga + */ +function buildDownloadRequest(params: RequestDownloadParams): string { + const { credential, token, rfc, fechaInicio, fechaFin, tipoSolicitud, tipoCfdi } = params; + const uuid = randomUUID(); + + const certificate = credential.certificate(); + const cerB64 = Buffer.from(certificate.pem().replace(/-----.*-----/g, '').replace(/\n/g, '')).toString('base64'); + + // Construir el elemento de solicitud + const rfcEmisor = tipoCfdi === 'emitidos' ? rfc : undefined; + const rfcReceptor = tipoCfdi === 'recibidos' ? rfc : undefined; + + const solicitudContent = `${rfc}` + + `${formatSatDate(fechaInicio)}` + + `${formatSatDate(fechaFin)}` + + `${tipoSolicitud}` + + (rfcEmisor ? `${rfcEmisor}` : '') + + (rfcReceptor ? `${rfcReceptor}` : ''); + + const solicitudToSign = `${solicitudContent}`; + const digestValue = createHash('sha1').update(solicitudToSign).digest('base64'); + + const signedInfoXml = `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `${digestValue}` + + `` + + ``; + + const signatureBinary = credential.sign(signedInfoXml, 'sha1'); + const signatureValue = Buffer.from(signatureBinary, 'binary').toString('base64'); + + return ` + + + + + + + ${signedInfoXml} + ${signatureValue} + + + + ${certificate.issuerAsRfc4514()} + ${certificate.serialNumber().bytes()} + + ${cerB64} + + + + + + +`; +} + +/** + * Solicita la descarga de CFDIs al SAT + */ +export async function requestDownload(params: RequestDownloadParams): Promise { + const soapRequest = buildDownloadRequest(params); + + try { + const response = await fetch(SAT_SOLICITUD_URL, { + method: 'POST', + headers: { + 'Content-Type': 'text/xml;charset=UTF-8', + 'SOAPAction': 'http://DescargaMasivaTerceros.sat.gob.mx/ISolicitaDescargaService/SolicitaDescarga', + 'Authorization': `WRAP access_token="${params.token}"`, + }, + body: soapRequest, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Error HTTP ${response.status}: ${errorText}`); + } + + const responseXml = await response.text(); + return parseDownloadRequestResponse(responseXml); + } catch (error: any) { + console.error('[SAT Download Request Error]', error); + throw new Error(`Error al solicitar descarga: ${error.message}`); + } +} + +/** + * Parsea la respuesta de solicitud de descarga + */ +function parseDownloadRequestResponse(responseXml: string): SatDownloadRequestResponse { + const parser = new XMLParser({ + ignoreAttributes: false, + removeNSPrefix: true, + attributeNamePrefix: '@_', + }); + + const result = parser.parse(responseXml); + const envelope = result.Envelope || result['s:Envelope']; + const body = envelope?.Body || envelope?.['s:Body']; + const respuesta = body?.SolicitaDescargaResponse?.SolicitaDescargaResult; + + if (!respuesta) { + throw new Error('Respuesta inválida del SAT'); + } + + return { + idSolicitud: respuesta['@_IdSolicitud'] || '', + codEstatus: respuesta['@_CodEstatus'] || '', + mensaje: respuesta['@_Mensaje'] || '', + }; +} + +/** + * Verifica el estado de una solicitud de descarga + */ +export async function verifyRequest( + credential: Credential, + token: string, + rfc: string, + idSolicitud: string +): Promise { + const certificate = credential.certificate(); + const cerB64 = Buffer.from(certificate.pem().replace(/-----.*-----/g, '').replace(/\n/g, '')).toString('base64'); + + const verificaContent = ``; + const digestValue = createHash('sha1').update(verificaContent).digest('base64'); + + const signedInfoXml = `` + + `` + + `` + + `` + + `` + + `` + + `${digestValue}` + + `` + + ``; + + const signatureBinary = credential.sign(signedInfoXml, 'sha1'); + const signatureValue = Buffer.from(signatureBinary, 'binary').toString('base64'); + + const soapRequest = ` + + + + + + + ${signedInfoXml} + ${signatureValue} + + + + ${certificate.issuerAsRfc4514()} + ${certificate.serialNumber().bytes()} + + ${cerB64} + + + + + + +`; + + try { + const response = await fetch(SAT_VERIFICA_URL, { + method: 'POST', + headers: { + 'Content-Type': 'text/xml;charset=UTF-8', + 'SOAPAction': 'http://DescargaMasivaTerceros.sat.gob.mx/IVerificaSolicitudDescargaService/VerificaSolicitudDescarga', + 'Authorization': `WRAP access_token="${token}"`, + }, + body: soapRequest, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Error HTTP ${response.status}: ${errorText}`); + } + + const responseXml = await response.text(); + return parseVerifyResponse(responseXml); + } catch (error: any) { + console.error('[SAT Verify Error]', error); + throw new Error(`Error al verificar solicitud: ${error.message}`); + } +} + +/** + * Parsea la respuesta de verificación + */ +function parseVerifyResponse(responseXml: string): SatVerifyResponse { + const parser = new XMLParser({ + ignoreAttributes: false, + removeNSPrefix: true, + attributeNamePrefix: '@_', + }); + + const result = parser.parse(responseXml); + const envelope = result.Envelope || result['s:Envelope']; + const body = envelope?.Body || envelope?.['s:Body']; + const respuesta = body?.VerificaSolicitudDescargaResponse?.VerificaSolicitudDescargaResult; + + if (!respuesta) { + throw new Error('Respuesta de verificación inválida'); + } + + // Extraer paquetes + let paquetes: string[] = []; + const paquetesNode = respuesta.IdsPaquetes; + if (paquetesNode) { + if (Array.isArray(paquetesNode)) { + paquetes = paquetesNode; + } else if (typeof paquetesNode === 'string') { + paquetes = [paquetesNode]; + } + } + + return { + codEstatus: respuesta['@_CodEstatus'] || '', + estadoSolicitud: parseInt(respuesta['@_EstadoSolicitud'] || '0', 10), + codigoEstadoSolicitud: respuesta['@_CodigoEstadoSolicitud'] || '', + numeroCfdis: parseInt(respuesta['@_NumeroCFDIs'] || '0', 10), + mensaje: respuesta['@_Mensaje'] || '', + paquetes, + }; +} + +/** + * Descarga un paquete de CFDIs + */ +export async function downloadPackage( + credential: Credential, + token: string, + rfc: string, + idPaquete: string +): Promise { + const certificate = credential.certificate(); + const cerB64 = Buffer.from(certificate.pem().replace(/-----.*-----/g, '').replace(/\n/g, '')).toString('base64'); + + const descargaContent = ``; + const digestValue = createHash('sha1').update(descargaContent).digest('base64'); + + const signedInfoXml = `` + + `` + + `` + + `` + + `` + + `` + + `${digestValue}` + + `` + + ``; + + const signatureBinary = credential.sign(signedInfoXml, 'sha1'); + const signatureValue = Buffer.from(signatureBinary, 'binary').toString('base64'); + + const soapRequest = ` + + + + + + + ${signedInfoXml} + ${signatureValue} + + + + ${certificate.issuerAsRfc4514()} + ${certificate.serialNumber().bytes()} + + ${cerB64} + + + + + + +`; + + try { + const response = await fetch(SAT_DESCARGA_URL, { + method: 'POST', + headers: { + 'Content-Type': 'text/xml;charset=UTF-8', + 'SOAPAction': 'http://DescargaMasivaTerceros.sat.gob.mx/IDescargaMasivaService/Descargar', + 'Authorization': `WRAP access_token="${token}"`, + }, + body: soapRequest, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Error HTTP ${response.status}: ${errorText}`); + } + + const responseXml = await response.text(); + return parseDownloadResponse(responseXml); + } catch (error: any) { + console.error('[SAT Download Package Error]', error); + throw new Error(`Error al descargar paquete: ${error.message}`); + } +} + +/** + * Parsea la respuesta de descarga de paquete + */ +function parseDownloadResponse(responseXml: string): SatPackageResponse { + const parser = new XMLParser({ + ignoreAttributes: false, + removeNSPrefix: true, + attributeNamePrefix: '@_', + }); + + const result = parser.parse(responseXml); + const envelope = result.Envelope || result['s:Envelope']; + const body = envelope?.Body || envelope?.['s:Body']; + const respuesta = body?.RespuestaDescargaMasivaTercerosSalida?.Paquete; + + if (!respuesta) { + throw new Error('No se pudo obtener el paquete'); + } + + return { + paquete: respuesta, + }; +} + +/** + * Estados de solicitud del SAT + */ +export const SAT_REQUEST_STATES = { + ACCEPTED: 1, + IN_PROGRESS: 2, + COMPLETED: 3, + ERROR: 4, + REJECTED: 5, + EXPIRED: 6, +} as const; + +/** + * Verifica si la solicitud está completa + */ +export function isRequestComplete(estadoSolicitud: number): boolean { + return estadoSolicitud === SAT_REQUEST_STATES.COMPLETED; +} + +/** + * Verifica si la solicitud falló + */ +export function isRequestFailed(estadoSolicitud: number): boolean { + return ( + estadoSolicitud === SAT_REQUEST_STATES.ERROR || + estadoSolicitud === SAT_REQUEST_STATES.REJECTED || + estadoSolicitud === SAT_REQUEST_STATES.EXPIRED + ); +} + +/** + * Verifica si la solicitud está en progreso + */ +export function isRequestInProgress(estadoSolicitud: number): boolean { + return ( + estadoSolicitud === SAT_REQUEST_STATES.ACCEPTED || + estadoSolicitud === SAT_REQUEST_STATES.IN_PROGRESS + ); +} diff --git a/apps/api/src/services/sat/sat-opinion-login.ts b/apps/api/src/services/sat/sat-opinion-login.ts new file mode 100644 index 0000000..4b23d97 --- /dev/null +++ b/apps/api/src/services/sat/sat-opinion-login.ts @@ -0,0 +1,92 @@ +import { type Page } from 'playwright'; + +const TIMEOUT = 60_000; + +export async function loginToSatOpinion( + page: Page, + cerPath: string, + keyPath: string, + password: string, +): Promise { + // Step 1: Navigate to SAT public page + const publicUrl = 'https://www.sat.gob.mx/portal/public/tramites/opinion-del-cumplimiento'; + console.log('[SAT Opinion] Navigating to SAT public page...'); + await page.goto(publicUrl, { waitUntil: 'domcontentloaded', timeout: TIMEOUT }); + await page.waitForTimeout(2000); + + // Step 2: Click "Obtén la Opinión del cumplimiento" tab + console.log('[SAT Opinion] Clicking "Obtén la Opinión del cumplimiento"...'); + const obtenerOpcion = page.locator('text=Obt').first(); + await obtenerOpcion.waitFor({ state: 'visible', timeout: TIMEOUT }); + await obtenerOpcion.click(); + await page.waitForTimeout(2000); + + // Step 3: Expand "De tu empresa" accordion + console.log('[SAT Opinion] Expanding "De tu empresa" section...'); + const empresaOption = page.locator('text=De tu empresa').first(); + await empresaOption.waitFor({ state: 'visible', timeout: TIMEOUT }); + await empresaOption.click(); + await page.waitForTimeout(2000); + + // Step 4: Click "Ingresa" link — opens new tab (target=_blank) + console.log('[SAT Opinion] Clicking "Ingresa" (opens new tab)...'); + const ingresaLink = page.locator('a:has-text("Ingresa")').first(); + await ingresaLink.waitFor({ state: 'visible', timeout: TIMEOUT }); + + const [loginPage] = await Promise.all([ + page.context().waitForEvent('page', { timeout: TIMEOUT }), + ingresaLink.click(), + ]); + await loginPage.waitForLoadState('domcontentloaded', { timeout: TIMEOUT }); + await loginPage.waitForTimeout(2000); + + // Step 5: Switch to e.firma login + console.log('[SAT Opinion] Clicking e.firma button...'); + const efirmaButton = loginPage.locator('button:has-text("e.firma"), input[value*="firma"], a:has-text("e.firma")').first(); + await efirmaButton.waitFor({ state: 'visible', timeout: TIMEOUT }); + await efirmaButton.click(); + await loginPage.waitForLoadState('domcontentloaded', { timeout: TIMEOUT }); + await loginPage.waitForTimeout(2000); + + // Step 6: Upload .cer + console.log('[SAT Opinion] Uploading .cer...'); + const cerInput = loginPage.locator('input[type="file"]').first(); + await cerInput.waitFor({ state: 'attached', timeout: TIMEOUT }); + await cerInput.setInputFiles(cerPath); + await loginPage.waitForTimeout(500); + + // Step 7: Upload .key + console.log('[SAT Opinion] Uploading .key...'); + const keyInput = loginPage.locator('input[type="file"]').nth(1); + await keyInput.waitFor({ state: 'attached', timeout: TIMEOUT }); + await keyInput.setInputFiles(keyPath); + await loginPage.waitForTimeout(500); + + // Step 8: Enter password + console.log('[SAT Opinion] Entering password...'); + const passwordInput = loginPage.locator('input[type="password"]').first(); + await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUT }); + await passwordInput.fill(password); + + // Step 9: Submit + console.log('[SAT Opinion] Submitting login...'); + const submitButton = loginPage.locator('button:has-text("Enviar"), input[value="Enviar"], a:has-text("Enviar"), input[type="submit"], button[type="submit"]').first(); + await submitButton.waitFor({ state: 'visible', timeout: TIMEOUT }); + await submitButton.click(); + + // Step 10: Wait for auth + redirect to report + console.log('[SAT Opinion] Waiting for authentication...'); + await loginPage.waitForTimeout(8000); + + const currentUrl = loginPage.url(); + if (!currentUrl.includes('reporteOpinion32DContribuyente')) { + const baseUrl = currentUrl.replace(/#.*$/, '').replace(/\?.*$/, ''); + const reporteUrl = baseUrl + '#/reporteOpinion32DContribuyente'; + console.log(`[SAT Opinion] Navigating to report: ${reporteUrl}`); + await loginPage.goto(reporteUrl, { waitUntil: 'domcontentloaded', timeout: TIMEOUT }); + await loginPage.waitForTimeout(5000); + } + + console.log('[SAT Opinion] Login completed.'); + return loginPage; +} diff --git a/apps/api/src/services/sat/sat-opinion-parser.ts b/apps/api/src/services/sat/sat-opinion-parser.ts new file mode 100644 index 0000000..fd4b3cd --- /dev/null +++ b/apps/api/src/services/sat/sat-opinion-parser.ts @@ -0,0 +1,76 @@ +import { PDFParse } from 'pdf-parse'; + +export interface ParsedOpinion { + rfc: string; + razonSocial: string; + estatus: string; + folio: string; + cadenaOriginal: string; + fechaConsulta: string; +} + +export async function parseOpinionPdf(pdfBuffer: Buffer): Promise { + const pdfParse = new PDFParse({ data: new Uint8Array(pdfBuffer) }); + try { + const textResult = await pdfParse.getText(); + const text = textResult.text; + + return { + rfc: extractRfc(text), + razonSocial: extractRazonSocial(text), + estatus: extractEstatus(text), + folio: extractFolio(text), + cadenaOriginal: extractCadenaOriginal(text), + fechaConsulta: extractFecha(text), + }; + } finally { + await pdfParse.destroy(); + } +} + +function extractRfc(text: string): string { + const match = text.match(/RFC\s+Folio\s*\n\s*([A-ZÑ&]{3,4}\d{6}[A-Z\d]{3})/i); + if (match) return match[1].trim(); + const fallback = text.match(/\b([A-ZÑ&]{3,4}\d{6}[A-Z\d]{3})\b/); + return fallback ? fallback[1] : 'NO_ENCONTRADO'; +} + +function extractRazonSocial(text: string): string { + const match = text.match(/(?:Nombre|denominaci[oó]n|raz[oó]n social)\s+Sentido\s*\n\s*(.+)/i); + if (match) { + return match[1].trim().replace(/\s+(POSITIVO|NEGATIVO|EN SUSPENSI[OÓ]N|NO INSCRITO|INSCRITO SIN OBLIGACIONES)\s*$/i, '').trim(); + } + return 'NO_ENCONTRADO'; +} + +function extractEstatus(text: string): string { + const match = text.match(/Sentido\s*\n\s*.+\s+(POSITIVO|NEGATIVO|EN SUSPENSI[OÓ]N|NO INSCRITO|INSCRITO SIN OBLIGACIONES)\s*$/im); + if (match) { + const raw = match[1].trim().toUpperCase(); + if (raw === 'POSITIVO') return 'Positiva'; + if (raw === 'NEGATIVO') return 'Negativa'; + if (raw.includes('SUSPENSI')) return 'En suspensión'; + if (raw.includes('NO INSCRITO')) return 'No inscrito'; + if (raw.includes('SIN OBLIGACIONES')) return 'Inscrito sin obligaciones'; + } + if (/POSITIVO/i.test(text)) return 'Positiva'; + if (/NEGATIVO/i.test(text)) return 'Negativa'; + return 'NO_DETERMINADO'; +} + +function extractFolio(text: string): string { + const match = text.match(/RFC\s+Folio\s*\n\s*[A-ZÑ&]{3,4}\d{6}[A-Z\d]{3}\s+(\S+)/i); + return match ? match[1].trim() : 'NO_ENCONTRADO'; +} + +function extractCadenaOriginal(text: string): string { + const match = text.match(/Cadena Original\s*\n\s*(\|\|.+\|\|)/i); + return match ? match[1].trim() : 'NO_ENCONTRADO'; +} + +function extractFecha(text: string): string { + const match = text.match(/Fecha\s+y\s+hora\s+de\s+emisi[oó]n\s*\n\s*(.+)/i); + if (match) return match[1].trim(); + const fallback = text.match(/(\d{1,2}\s+de\s+\w+\s+de\s+\d{4}\s+a\s+las\s+[\d:]+\s+horas)/i); + return fallback ? fallback[1].trim() : 'NO_ENCONTRADO'; +} diff --git a/apps/api/src/services/sat/sat-opinion-scraper.ts b/apps/api/src/services/sat/sat-opinion-scraper.ts new file mode 100644 index 0000000..722229b --- /dev/null +++ b/apps/api/src/services/sat/sat-opinion-scraper.ts @@ -0,0 +1,84 @@ +import { type Page } from 'playwright'; + +export async function extractOpinionPdf(page: Page): Promise { + const TIMEOUT = 120_000; + const POLL_INTERVAL = 3_000; + + console.log('[SAT Opinion Scraper] Waiting for PDF to appear...'); + + let interceptedPdf: Buffer | null = null; + page.on('response', async (response) => { + try { + const contentType = response.headers()['content-type'] || ''; + if (contentType.includes('application/pdf') || response.url().endsWith('.pdf')) { + const body = await response.body(); + if (body.length > 100) { + interceptedPdf = body; + console.log(`[SAT Opinion Scraper] PDF intercepted via network: ${body.length} bytes`); + } + } + } catch { /* response body may not be available */ } + }); + + const startTime = Date.now(); + + while (Date.now() - startTime < TIMEOUT) { + if (interceptedPdf) return interceptedPdf; + + // Strategy 1: or with PDF data URI + const embedData = await page.evaluate(() => { + for (const el of document.querySelectorAll('embed, object')) { + const src = el.getAttribute('src') || el.getAttribute('data') || ''; + if (src.startsWith('data:application/pdf;base64,')) return src; + } + return null; + }).catch(() => null); + + if (embedData) { + console.log('[SAT Opinion Scraper] PDF found via /'); + return decodeDataUri(embedData); + } + + // Strategy 2: Scan full HTML for base64 PDF + const html = await page.content().catch(() => ''); + const match = html.match(/data:application\/pdf;base64,([A-Za-z0-9+/=\s]+)/); + if (match) { + console.log('[SAT Opinion Scraper] PDF found via page content scan'); + return decodeDataUri(`data:application/pdf;base64,${match[1]}`); + } + + // Strategy 3: Check iframes + for (const frame of page.frames()) { + try { + const frameUrl = frame.url(); + if (frameUrl.startsWith('data:application/pdf;base64,')) { + console.log('[SAT Opinion Scraper] PDF found via iframe URL'); + return decodeDataUri(frameUrl); + } + const frameHtml = await frame.content(); + const frameMatch = frameHtml.match(/data:application\/pdf;base64,([A-Za-z0-9+/=\s]+)/); + if (frameMatch) { + console.log('[SAT Opinion Scraper] PDF found via iframe content'); + return decodeDataUri(`data:application/pdf;base64,${frameMatch[1]}`); + } + } catch { /* cross-origin frame */ } + } + + // Strategy 4: Page URL itself + if (page.url().startsWith('data:application/pdf;base64,')) { + console.log('[SAT Opinion Scraper] PDF found via page URL'); + return decodeDataUri(page.url()); + } + + console.log(`[SAT Opinion Scraper] PDF not found, retrying in ${POLL_INTERVAL / 1000}s...`); + await page.waitForTimeout(POLL_INTERVAL); + } + + throw new Error(`PDF not found after ${TIMEOUT / 1000}s`); +} + +function decodeDataUri(dataUri: string): Buffer { + const prefix = 'data:application/pdf;base64,'; + const base64 = dataUri.substring(prefix.length).replace(/\s/g, ''); + return Buffer.from(base64, 'base64'); +} diff --git a/apps/api/src/services/sat/sat-parser.service.ts b/apps/api/src/services/sat/sat-parser.service.ts new file mode 100644 index 0000000..b7d1056 --- /dev/null +++ b/apps/api/src/services/sat/sat-parser.service.ts @@ -0,0 +1,735 @@ +import AdmZip from 'adm-zip'; +import { XMLParser } from 'fast-xml-parser'; +import type { TipoCfdi, EstadoCfdi } from '@horux/shared'; + +interface CfdiParsed { + uuid: string; + type: TipoCfdi; + tipoComprobante: string; + serie: string | null; + folio: string | null; + status: EstadoCfdi; + fechaEmision: Date; + fechaCertSat: Date | null; + rfcEmisor: string; + nombreEmisor: string; + rfcReceptor: string; + nombreReceptor: string; + subtotal: number; + descuento: number; + total: number; + moneda: string; + tipoCambio: number; + metodoPago: string | null; + formaPago: string | null; + usoCfdi: string | null; + pac: string | null; + // Impuestos del comprobante + ivaTraslado: number; + isrRetencion: number; + ivaRetencion: number; + iepsTraslado: number; + iepsRetencion: number; + // Impuestos locales + impuestosLocalesTrasladado: number; + impuestosLocalesRetenidos: number; + // Complemento de pagos + montoPago: number; + fechaPagoP: string | null; + numParcialidad: string | null; + uuidRelacionado: string | null; + saldoInsoluto: string | null; + isrRetencionPago: number; + ivaTrasladoPago: number; + ivaRetencionPago: number; + iepsTrasladoPago: number; + iepsRetencionPago: number; + // Nómina + fechaPago: string | null; + fechaInicialPago: string | null; + fechaFinalPago: string | null; + numDiasPagados: number; + numSeguroSocial: string | null; + puesto: string | null; + salarioBaseCotApor: number; + salarioDiarioIntegrado: number; + totalPercepciones: number; + totalDeducciones: number; + impRetenidosNomina: number; + otrasDeduccionesNomina: number; + subsidioCausado: number; + + regimenFiscalEmisor: string | null; + regimenFiscalReceptor: string | null; + // CfdiRelacionados a nivel raíz del comprobante (CFDI 4.0). + // `cfdiTipoRelacion` — clave SAT (01..07). NULL si no hay relación. + // `cfdisRelacionados` — UUIDs pipe-separated. + cfdiTipoRelacion: string | null; + cfdisRelacionados: string | null; + conceptos: ConceptoParsed[]; + xmlOriginal: string; +} + +interface ConceptoParsed { + claveProdServ: string | null; + noIdentificacion: string | null; + descripcion: string; + cantidad: number; + claveUnidad: string | null; + unidad: string | null; + valorUnitario: number; + importe: number; + descuento: number; + // Impuestos por concepto + isrRetencion: number; + ivaTraslado: number; + ivaRetencion: number; + iepsTraslado: number; + iepsRetencion: number; +} + +interface ExtractedXml { + filename: string; + content: string; +} + +const xmlParser = new XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: '@_', + removeNSPrefix: true, +}); + +/** + * Extrae archivos XML de un paquete ZIP en base64 + */ +export function extractXmlsFromZip(zipBase64: string): ExtractedXml[] { + const zipBuffer = Buffer.from(zipBase64, 'base64'); + const zip = new AdmZip(zipBuffer); + const entries = zip.getEntries(); + + const xmlFiles: ExtractedXml[] = []; + + for (const entry of entries) { + if (entry.entryName.toLowerCase().endsWith('.xml')) { + const content = entry.getData().toString('utf-8'); + xmlFiles.push({ + filename: entry.entryName, + content, + }); + } + } + + return xmlFiles; +} + +/** + * Parsea una fecha del XML/CSV del SAT preservando la hora **literal**. + * + * Problema: el CFDI 4.0 define `Fecha` y `FechaTimbrado` como ISO-8601 sin + * zona horaria (hora local del contribuyente = México). Si se pasa tal cual + * a `new Date(str)`, Node lo interpreta según la timezone de la máquina: + * en CDMX (UTC-6), "2025-12-31T18:37:51" se convierte a UTC + * "2026-01-01T00:37:51Z", cambiando la fecha efectiva y desalineando el + * mes/año del CFDI. Postgres guarda ese valor UTC, y los filtros por rango + * lo sacan del mes correcto. + * + * Solución: forzar 'Z' si el string no trae TZ indicator. Esto hace que + * Node interprete el texto como UTC literal y preserve la hora tal cual. + * El valor queda naive pero consistente: todo el sistema filtra con + * fechas naive (sin TZ), así que el resultado es correcto. + */ +function parseCfdiDate(str: string | null | undefined): Date { + if (!str) return new Date(0); + const s = String(str).trim(); + if (!s) return new Date(0); + const hasTz = /[Zz]|[+-]\d{2}:?\d{2}$/.test(s); + return new Date(hasTz ? s : s + 'Z'); +} + +function toArray(val: any): any[] { + if (!val) return []; + return Array.isArray(val) ? val : [val]; +} + +function pf(val: any): number { + return parseFloat(val || '0') || 0; +} + +/** + * Extrae el UUID del TimbreFiscalDigital + */ +function extractUuid(comprobante: any): string { + return comprobante.Complemento?.TimbreFiscalDigital?.['@_UUID'] || ''; +} + +/** + * Extrae datos del timbre: fecha cert SAT y PAC + */ +function extractTimbreData(comprobante: any): { fechaCertSat: Date | null; pac: string | null } { + const timbre = comprobante.Complemento?.TimbreFiscalDigital; + if (!timbre) return { fechaCertSat: null, pac: null }; + + return { + fechaCertSat: timbre['@_FechaTimbrado'] ? parseCfdiDate(timbre['@_FechaTimbrado']) : null, + pac: timbre['@_RfcProvCertif'] || null, + }; +} + +/** + * Extrae impuestos trasladados (IVA 002, IEPS 003) + */ +function extractTraslados(comprobante: any): { iva: number; ieps: number } { + const traslados = toArray(comprobante.Impuestos?.Traslados?.Traslado); + let iva = 0, ieps = 0; + + for (const t of traslados) { + const importe = pf(t['@_Importe']); + if (t['@_Impuesto'] === '002') iva += importe; + else if (t['@_Impuesto'] === '003') ieps += importe; + } + + return { iva, ieps }; +} + +/** + * Extrae impuestos retenidos (ISR 001, IVA 002, IEPS 003) + */ +function extractRetenciones(comprobante: any): { isr: number; iva: number; ieps: number } { + const retenciones = toArray(comprobante.Impuestos?.Retenciones?.Retencion); + let isr = 0, iva = 0, ieps = 0; + + for (const r of retenciones) { + const importe = pf(r['@_Importe']); + if (r['@_Impuesto'] === '001') isr += importe; + else if (r['@_Impuesto'] === '002') iva += importe; + else if (r['@_Impuesto'] === '003') ieps += importe; + } + + return { isr, iva, ieps }; +} + +/** + * Extrae impuestos locales + */ +function extractImpuestosLocales(comprobante: any): { trasladado: number; retenido: number } { + const complemento = comprobante.Complemento; + if (!complemento) return { trasladado: 0, retenido: 0 }; + + const impLocales = complemento.ImpuestosLocales; + if (!impLocales) return { trasladado: 0, retenido: 0 }; + + return { + trasladado: pf(impLocales['@_TotaldeTraslados']), + retenido: pf(impLocales['@_TotaldeRetenciones']), + }; +} + +/** + * Extrae CfdiRelacionados a nivel raíz del Comprobante. Puede haber 1+ + * nodos `cfdi:CfdiRelacionados` (cada uno con un `TipoRelacion`), y dentro + * de cada uno 1+ `cfdi:CfdiRelacionado` con UUID. Retorna el primer + * TipoRelacion encontrado (lo más común) y todos los UUIDs pipe-separated. + */ +function extractCfdiRelacionados(comprobante: any): { + tipoRelacion: string | null; + uuids: string | null; +} { + const nodes = toArray(comprobante.CfdiRelacionados); + if (nodes.length === 0) return { tipoRelacion: null, uuids: null }; + + let tipoRelacion: string | null = null; + const allUuids: string[] = []; + + for (const node of nodes) { + if (!tipoRelacion && node['@_TipoRelacion']) { + tipoRelacion = String(node['@_TipoRelacion']); + } + const rels = toArray(node.CfdiRelacionado); + for (const r of rels) { + if (r['@_UUID']) allUuids.push(String(r['@_UUID'])); + } + } + + return { + tipoRelacion, + uuids: allUuids.length > 0 ? allUuids.join('|') : null, + }; +} + +/** + * Extrae datos del complemento de pagos (pago20) + */ +function extractPagos(comprobante: any): { + montoPago: number; + fechaPagoP: string | null; + numParcialidad: string | null; + uuidRelacionado: string | null; + saldoInsoluto: string | null; + isrRetencion: number; + ivaTraslado: number; + ivaRetencion: number; + iepsTraslado: number; + iepsRetencion: number; +} { + const result = { + montoPago: 0, fechaPagoP: null as string | null, + numParcialidad: null as string | null, + uuidRelacionado: null as string | null, + saldoInsoluto: null as string | null, + isrRetencion: 0, ivaTraslado: 0, ivaRetencion: 0, + iepsTraslado: 0, iepsRetencion: 0, + }; + + const complemento = comprobante.Complemento; + if (!complemento) return result; + + // Try pago20:Pagos or just Pagos + const pagosNode = complemento.Pagos; + if (!pagosNode) return result; + + const pagos = toArray(pagosNode.Pago); + const fechas: string[] = []; + const parcialidades: string[] = []; + const uuids: string[] = []; + const saldos: string[] = []; + + for (const pago of pagos) { + result.montoPago += pf(pago['@_Monto']); + if (pago['@_FechaPago']) fechas.push(pago['@_FechaPago']); + + // Impuestos del pago + const retPago = toArray(pago.ImpuestosP?.RetencionesPP?.RetencionP || pago.ImpuestosP?.RetencionesPP); + for (const r of retPago) { + const importe = pf(r['@_ImporteP']); + if (r['@_ImpuestoP'] === '001') result.isrRetencion += importe; + else if (r['@_ImpuestoP'] === '002') result.ivaRetencion += importe; + else if (r['@_ImpuestoP'] === '003') result.iepsRetencion += importe; + } + + const trasPago = toArray(pago.ImpuestosP?.TrasladosP?.TrasladoP || pago.ImpuestosP?.TrasladosP); + for (const t of trasPago) { + const importe = pf(t['@_ImporteP']); + if (t['@_ImpuestoP'] === '002') result.ivaTraslado += importe; + else if (t['@_ImpuestoP'] === '003') result.iepsTraslado += importe; + } + + // Documentos relacionados + const doctos = toArray(pago.DoctoRelacionado); + for (const d of doctos) { + if (d['@_IdDocumento']) uuids.push(d['@_IdDocumento']); + if (d['@_NumParcialidad']) parcialidades.push(d['@_NumParcialidad']); + if (d['@_ImpSaldoInsoluto'] !== undefined) saldos.push(d['@_ImpSaldoInsoluto']); + } + } + + result.fechaPagoP = fechas.length > 0 ? fechas.join('|') : null; + result.numParcialidad = parcialidades.length > 0 ? parcialidades.join('|') : null; + result.uuidRelacionado = uuids.length > 0 ? uuids.join('|') : null; + result.saldoInsoluto = saldos.length > 0 ? saldos.join('|') : null; + + return result; +} + +/** + * Extrae datos del complemento de nómina (nomina12) + */ +function extractNomina(comprobante: any): { + fechaPago: string | null; + fechaInicialPago: string | null; + fechaFinalPago: string | null; + numDiasPagados: number; + numSeguroSocial: string | null; + puesto: string | null; + salarioBaseCotApor: number; + salarioDiarioIntegrado: number; + totalPercepciones: number; + totalDeducciones: number; + impRetenidosNomina: number; + otrasDeduccionesNomina: number; + subsidioCausado: number; +} { + const result = { + fechaPago: null as string | null, + fechaInicialPago: null as string | null, + fechaFinalPago: null as string | null, + numDiasPagados: 0, + numSeguroSocial: null as string | null, + puesto: null as string | null, + salarioBaseCotApor: 0, + salarioDiarioIntegrado: 0, + totalPercepciones: 0, + totalDeducciones: 0, + impRetenidosNomina: 0, + otrasDeduccionesNomina: 0, + subsidioCausado: 0, + }; + + const complemento = comprobante.Complemento; + if (!complemento) return result; + + const nomina = complemento.Nomina; + if (!nomina) return result; + + result.fechaPago = nomina['@_FechaPago'] || null; + result.fechaInicialPago = nomina['@_FechaInicialPago'] || null; + result.fechaFinalPago = nomina['@_FechaFinalPago'] || null; + result.numDiasPagados = pf(nomina['@_NumDiasPagados']); + result.totalPercepciones = pf(nomina['@_TotalPercepciones']); + result.totalDeducciones = pf(nomina['@_TotalDeducciones']); + + // Receptor de nómina + const receptor = nomina.Receptor; + if (receptor) { + result.numSeguroSocial = receptor['@_NumSeguridadSocial'] || null; + result.puesto = receptor['@_Puesto'] || null; + result.salarioBaseCotApor = pf(receptor['@_SalarioBaseCotApor']); + result.salarioDiarioIntegrado = pf(receptor['@_SalarioDiarioIntegrado']); + } + + // Deducciones + const deducciones = nomina.Deducciones; + if (deducciones) { + result.impRetenidosNomina = pf(deducciones['@_TotalImpuestosRetenidos']); + result.otrasDeduccionesNomina = pf(deducciones['@_TotalOtrasDeducciones']); + } + + // Subsidio causado (OtrosPagos/OtroPago[@TipoOtroPago='002']) + const otrosPagos = toArray(nomina.OtrosPagos?.OtroPago); + for (const op of otrosPagos) { + if (op['@_TipoOtroPago'] === '002') { + result.subsidioCausado = pf(op.SubsidioAlEmpleo?.['@_SubsidioCausado']); + } + } + + return result; +} + +/** + * Extrae los conceptos del comprobante + */ +function extractConceptos(comprobante: any): ConceptoParsed[] { + const conceptosNode = comprobante.Conceptos?.Concepto; + if (!conceptosNode) return []; + + const conceptos = toArray(conceptosNode); + return conceptos.map((c: any) => { + // Impuestos por concepto + const trasladosC = toArray(c.Impuestos?.Traslados?.Traslado); + const retencionesC = toArray(c.Impuestos?.Retenciones?.Retencion); + + let ivaTraslado = 0, iepsTraslado = 0; + for (const t of trasladosC) { + const importe = pf(t['@_Importe']); + if (t['@_Impuesto'] === '002') ivaTraslado += importe; + else if (t['@_Impuesto'] === '003') iepsTraslado += importe; + } + + let isrRetencion = 0, ivaRetencion = 0, iepsRetencion = 0; + for (const r of retencionesC) { + const importe = pf(r['@_Importe']); + if (r['@_Impuesto'] === '001') isrRetencion += importe; + else if (r['@_Impuesto'] === '002') ivaRetencion += importe; + else if (r['@_Impuesto'] === '003') iepsRetencion += importe; + } + + return { + claveProdServ: c['@_ClaveProdServ'] || null, + noIdentificacion: c['@_NoIdentificacion'] || null, + descripcion: c['@_Descripcion'] || '', + cantidad: pf(c['@_Cantidad']) || 1, + claveUnidad: c['@_ClaveUnidad'] || null, + unidad: c['@_Unidad'] || null, + valorUnitario: pf(c['@_ValorUnitario']), + importe: pf(c['@_Importe']), + descuento: pf(c['@_Descuento']), + isrRetencion, + ivaTraslado, + ivaRetencion, + iepsTraslado, + iepsRetencion, + }; + }); +} + +/** + * Parsea un XML de CFDI y extrae los datos relevantes + * @param downloadType - 'emitidos' o 'recibidos' para determinar el type (EMITIDO/RECIBIDO) + */ +export function parseXml(xmlContent: string, downloadType: 'emitidos' | 'recibidos' = 'emitidos'): CfdiParsed | null { + try { + const result = xmlParser.parse(xmlContent); + const comprobante = result.Comprobante; + + if (!comprobante) { + console.error('[Parser] No se encontró el nodo Comprobante'); + return null; + } + + const emisor = comprobante.Emisor || {}; + const receptor = comprobante.Receptor || {}; + const retenciones = extractRetenciones(comprobante); + const traslados = extractTraslados(comprobante); + const timbreData = extractTimbreData(comprobante); + const impLocales = extractImpuestosLocales(comprobante); + const tipoComprobante = comprobante['@_TipoDeComprobante'] || 'I'; + + // Complemento de pagos (solo tipo P) + const pagosData = tipoComprobante === 'P' ? extractPagos(comprobante) : { + montoPago: 0, fechaPagoP: null, numParcialidad: null, + uuidRelacionado: null, saldoInsoluto: null, + isrRetencion: 0, ivaTraslado: 0, ivaRetencion: 0, + iepsTraslado: 0, iepsRetencion: 0, + }; + + // CfdiRelacionados a nivel raíz. CFDI 4.0 permite 1+ nodos + // `cfdi:CfdiRelacionados` cada uno con un TipoRelacion y múltiples UUIDs. + // Aquí capturamos el PRIMER TipoRelacion (lo más común es que haya uno + // solo, especialmente en NC tipo E). Los UUIDs de todos los bloques se + // concatenan con `|`. + const relacionesData = extractCfdiRelacionados(comprobante); + + // Complemento de nómina (solo tipo N) + const nominaData = tipoComprobante === 'N' ? extractNomina(comprobante) : { + fechaPago: null, fechaInicialPago: null, fechaFinalPago: null, + numDiasPagados: 0, numSeguroSocial: null, puesto: null, + salarioBaseCotApor: 0, salarioDiarioIntegrado: 0, + totalPercepciones: 0, totalDeducciones: 0, + impRetenidosNomina: 0, otrasDeduccionesNomina: 0, subsidioCausado: 0, + }; + + const cfdi: CfdiParsed = { + uuid: extractUuid(comprobante), + type: downloadType === 'emitidos' ? 'EMITIDO' : 'RECIBIDO', + tipoComprobante, + serie: comprobante['@_Serie'] || null, + folio: comprobante['@_Folio'] || null, + status: 'Vigente', + fechaEmision: parseCfdiDate(comprobante['@_Fecha']), + fechaCertSat: timbreData.fechaCertSat, + rfcEmisor: emisor['@_Rfc'] || '', + nombreEmisor: emisor['@_Nombre'] || '', + rfcReceptor: receptor['@_Rfc'] || '', + nombreReceptor: receptor['@_Nombre'] || '', + subtotal: pf(comprobante['@_SubTotal']), + descuento: pf(comprobante['@_Descuento']), + total: pf(comprobante['@_Total']), + moneda: comprobante['@_Moneda'] || 'MXN', + tipoCambio: pf(comprobante['@_TipoCambio']) || 1, + metodoPago: comprobante['@_MetodoPago'] || null, + formaPago: comprobante['@_FormaPago'] || null, + usoCfdi: receptor['@_UsoCFDI'] || null, + pac: timbreData.pac, + regimenFiscalEmisor: emisor['@_RegimenFiscal'] || null, + regimenFiscalReceptor: receptor['@_RegimenFiscalReceptor'] || receptor['@_RegimenFiscal'] || null, + cfdiTipoRelacion: relacionesData.tipoRelacion, + cfdisRelacionados: relacionesData.uuids, + // Impuestos comprobante + ivaTraslado: traslados.iva, + isrRetencion: retenciones.isr, + ivaRetencion: retenciones.iva, + iepsTraslado: traslados.ieps, + iepsRetencion: retenciones.ieps, + // Impuestos locales + impuestosLocalesTrasladado: impLocales.trasladado, + impuestosLocalesRetenidos: impLocales.retenido, + // Complemento de pagos + montoPago: pagosData.montoPago, + fechaPagoP: pagosData.fechaPagoP, + numParcialidad: pagosData.numParcialidad, + uuidRelacionado: pagosData.uuidRelacionado, + saldoInsoluto: pagosData.saldoInsoluto, + isrRetencionPago: pagosData.isrRetencion, + ivaTrasladoPago: pagosData.ivaTraslado, + ivaRetencionPago: pagosData.ivaRetencion, + iepsTrasladoPago: pagosData.iepsTraslado, + iepsRetencionPago: pagosData.iepsRetencion, + // Nómina + ...nominaData, + conceptos: extractConceptos(comprobante), + xmlOriginal: xmlContent, + }; + + if (!cfdi.uuid) { + console.error('[Parser] CFDI sin UUID'); + return null; + } + + return cfdi; + } catch (error) { + console.error('[Parser Error]', error); + return null; + } +} + +/** + * Procesa un paquete ZIP completo y retorna los CFDIs parseados + * @param downloadType - 'emitidos' o 'recibidos' + */ +export function processPackage(zipBase64: string, downloadType: 'emitidos' | 'recibidos' = 'emitidos'): CfdiParsed[] { + const xmlFiles = extractXmlsFromZip(zipBase64); + const cfdis: CfdiParsed[] = []; + + for (const { content } of xmlFiles) { + const cfdi = parseXml(content, downloadType); + if (cfdi) { + cfdis.push(cfdi); + } + } + + return cfdis; +} + +/** + * Datos parseados de un registro de metadata del SAT + */ +interface CfdiMetadata { + uuid: string; + rfcEmisor: string; + nombreEmisor: string; + rfcReceptor: string; + nombreReceptor: string; + rfcPac: string | null; + fechaEmision: Date; + fechaCertSat: Date | null; + fechaCancelacion: Date | null; + monto: number; + tipoComprobante: string; + status: string; // 'Vigente' | 'Cancelado' + type: 'EMITIDO' | 'RECIBIDO'; +} + +/** + * Extrae archivos CSV de un paquete ZIP de metadata en base64. + * Usa AdmZip directamente para evitar problemas de archivos temporales en Windows. + */ +function extractCsvsFromZip(zipBase64: string): string[] { + const zipBuffer = Buffer.from(zipBase64, 'base64'); + const zip = new AdmZip(zipBuffer); + const entries = zip.getEntries(); + const csvContents: string[] = []; + + for (const entry of entries) { + const name = entry.entryName.toLowerCase(); + if (name.endsWith('.csv') || name.endsWith('.txt')) { + csvContents.push(entry.getData().toString('utf-8')); + } + } + + return csvContents; +} + +/** + * Parsea una línea CSV respetando campos entrecomillados + */ +function parseCsvLine(line: string): string[] { + const fields: string[] = []; + let current = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const ch = line[i]; + if (ch === '"') { + inQuotes = !inQuotes; + } else if (ch === '~' && !inQuotes) { + fields.push(current.trim()); + current = ''; + } else { + current += ch; + } + } + fields.push(current.trim()); + return fields; +} + +/** + * Procesa un paquete de metadata del SAT (ZIP con CSV) y retorna los registros. + * Usa AdmZip directo en vez de MetadataPackageReader para compatibilidad Windows. + */ +export function processMetadataPackage( + zipBase64: string, + downloadType: 'emitidos' | 'recibidos' = 'emitidos' +): CfdiMetadata[] { + const csvContents = extractCsvsFromZip(zipBase64); + const results: CfdiMetadata[] = []; + + const tipoMap: Record = { + 'Ingreso': 'I', 'Egreso': 'E', 'Traslado': 'T', 'Nómina': 'N', 'Nomina': 'N', 'Pago': 'P', + }; + + for (const csv of csvContents) { + const lines = csv.split(/\r?\n/).filter(l => l.trim()); + if (lines.length < 2) continue; + + // Header line — SAT uses ~ as delimiter + const headers = parseCsvLine(lines[0]); + + // Find column indices (case-insensitive) + const idx = (name: string) => headers.findIndex(h => h.toLowerCase() === name.toLowerCase()); + const iUuid = idx('Uuid'); + const iRfcEmisor = idx('RfcEmisor'); + const iNombreEmisor = idx('NombreEmisor'); + const iRfcReceptor = idx('RfcReceptor'); + const iNombreReceptor = idx('NombreReceptor'); + const iRfcPac = idx('RfcPac'); + const iFechaEmision = idx('FechaEmision'); + const iFechaCert = idx('FechaCertificacionSat'); + const iFechaCancel = idx('FechaCancelacion'); + const iMonto = idx('Monto'); + const iEfecto = idx('EfectoComprobante'); + const iEstatus = idx('Estatus'); + // Fallback column names + const iEstado = iEstatus >= 0 ? iEstatus : idx('Estado'); + + if (iUuid < 0) continue; // No UUID column = invalid CSV + + for (let i = 1; i < lines.length; i++) { + const fields = parseCsvLine(lines[i]); + const uuid = (fields[iUuid] || '').trim(); + if (!uuid) continue; + + const estatus = (fields[iEstado] || 'Vigente').trim(); + const fechaCancelStr = iFechaCancel >= 0 ? (fields[iFechaCancel] || '').trim() : ''; + const fechaEmisionStr = iFechaEmision >= 0 ? (fields[iFechaEmision] || '').trim() : ''; + const fechaCertStr = iFechaCert >= 0 ? (fields[iFechaCert] || '').trim() : ''; + const efecto = iEfecto >= 0 ? (fields[iEfecto] || 'Ingreso').trim() : 'Ingreso'; + + results.push({ + uuid: uuid.toUpperCase(), + rfcEmisor: iRfcEmisor >= 0 ? (fields[iRfcEmisor] || '').trim() : '', + nombreEmisor: iNombreEmisor >= 0 ? (fields[iNombreEmisor] || '').trim() : '', + rfcReceptor: iRfcReceptor >= 0 ? (fields[iRfcReceptor] || '').trim() : '', + nombreReceptor: iNombreReceptor >= 0 ? (fields[iNombreReceptor] || '').trim() : '', + rfcPac: iRfcPac >= 0 ? (fields[iRfcPac] || '').trim() || null : null, + fechaEmision: fechaEmisionStr ? parseCfdiDate(fechaEmisionStr) : new Date(), + fechaCertSat: fechaCertStr ? parseCfdiDate(fechaCertStr) : null, + fechaCancelacion: fechaCancelStr ? parseCfdiDate(fechaCancelStr) : null, + monto: parseFloat(iMonto >= 0 ? fields[iMonto] || '0' : '0') || 0, + tipoComprobante: tipoMap[efecto] || efecto.charAt(0) || 'I', + status: estatus === '0' || estatus.toLowerCase().includes('cancel') ? 'Cancelado' : 'Vigente', + type: downloadType === 'emitidos' ? 'EMITIDO' : 'RECIBIDO', + }); + } + } + + return results; +} + +/** + * Valida que un XML sea un CFDI válido + */ +export function isValidCfdi(xmlContent: string): boolean { + try { + const result = xmlParser.parse(xmlContent); + const comprobante = result.Comprobante; + + if (!comprobante) return false; + if (!comprobante.Complemento?.TimbreFiscalDigital) return false; + if (!extractUuid(comprobante)) return false; + + return true; + } catch { + return false; + } +} + +export type { CfdiParsed, CfdiMetadata, ConceptoParsed, ExtractedXml }; diff --git a/apps/api/src/services/sat/sat.service.ts b/apps/api/src/services/sat/sat.service.ts new file mode 100644 index 0000000..be371f3 --- /dev/null +++ b/apps/api/src/services/sat/sat.service.ts @@ -0,0 +1,1463 @@ +import { prisma, tenantDb } from '../../config/database.js'; +import { getDecryptedFiel } from '../fiel.service.js'; +import { getDecryptedFielContribuyente } from '../contribuyente-fiel.service.js'; +import { markForInvalidation } from '../metricas.service.js'; +import { + createSatService, + querySat, + verifySatRequest, + downloadSatPackage, + type FielData, +} from './sat-client.service.js'; +import { processPackage, processMetadataPackage, extractXmlsFromZip, type CfdiParsed, type CfdiMetadata } from './sat-parser.service.js'; +import { recomputarSaldoPendiente, uuidsAfectadosPorCfdi } from '../../utils/saldo.js'; +import type { SatSyncJob, CfdiSyncType, SatSyncType } from '@horux/shared'; +import type { Service } from '@nodecfdi/sat-ws-descarga-masiva'; +import type { Pool } from 'pg'; +import * as fs from 'fs'; +import * as path from 'path'; + +const POLL_INTERVAL_MS = 60000; // 60 segundos +const MAX_POLL_ATTEMPTS = 45; // 45 minutos máximo (45 × 60s) +const YEARS_TO_SYNC = 6; // SAT solo permite descargar últimos 6 años + +/** + * Política de retry por tipo de sync. + * - `retryAtHours[i]` = horas DESDE startedAt para el retry i+1. + * Los tiempos son ABSOLUTOS desde el inicio, no acumulativos desde el + * intento anterior — así el retry 1 cae exactamente a startedAt+6h sin + * importar cuánto tardó el intento original en hacer timeout. + * - `maxRetries` = nº máximo de reintentos (después → status='failed'). + * + * Justificación de las políticas: + * - daily/custom: el contador puede esperar 12h. 2 retries cubre fallas + * transitorias del SAT sin atascar pendientes infinitamente. + * - initial bootstrap: la primera sync de un tenant puede ser de 6 años + * de datos — vale la pena más paciencia (24h del start es el último + * intento). Si después de 24h sigue fallando, hay un problema estructural. + * - incremental: corre cada 4h por el cron. Si una falla, la siguiente + * ejecución cubrirá el gap (la ventana de 8h se solapa). Reintentar + * duplicaría carga sin beneficio. + */ +const RETRY_POLICIES: Record<'daily' | 'custom' | 'initial' | 'incremental', { + maxRetries: number; + retryAtHours: number[]; +}> = { + daily: { maxRetries: 2, retryAtHours: [6, 12] }, + custom: { maxRetries: 2, retryAtHours: [6, 12] }, + initial: { maxRetries: 3, retryAtHours: [6, 12, 24] }, + incremental: { maxRetries: 0, retryAtHours: [] }, +}; + +function getRetryPolicy(job: { type: SatSyncType; isCustomRange: boolean }) { + if (job.type === 'initial' && job.isCustomRange) return RETRY_POLICIES.custom; + return RETRY_POLICIES[job.type]; +} + +/** + * Calcula `nextRetryAt` para el retry número `nextRetryNumber` (1-based). + * Devuelve null si ya no hay retries disponibles según la política. + */ +function computeNextRetryAt( + startedAt: Date, + nextRetryNumber: number, + policy: { retryAtHours: number[] }, +): Date | null { + const idx = nextRetryNumber - 1; + if (idx < 0 || idx >= policy.retryAtHours.length) return null; + return new Date(startedAt.getTime() + policy.retryAtHours[idx] * 60 * 60 * 1000); +} + +interface SyncContext { + fielData: FielData; + service: Service; + rfc: string; + tenantId: string; + databaseName: string; + contribuyenteId: string | null; + getPool: () => Promise; +} + +/** + * Actualiza el progreso de un job + */ +async function updateJobProgress( + jobId: string, + updates: Partial<{ + status: 'pending' | 'running' | 'completed' | 'failed'; + satRequestId: string; + satPackageIds: string[]; + cfdisFound: number; + cfdisDownloaded: number; + cfdisInserted: number; + cfdisUpdated: number; + progressPercent: number; + errorMessage: string; + startedAt: Date; + completedAt: Date; + retryCount: number; + nextRetryAt: Date; + }> +): Promise { + await prisma.satSyncJob.update({ + where: { id: jobId }, + data: updates, + }); +} + +/** + * Obtiene o crea un RFC en la tabla rfcs del tenant + */ +async function getOrCreateRfc(pool: Pool, rfc: string, razonSocial: string | null, regimenFiscal?: string | null): Promise { + const { rows } = await pool.query( + `INSERT INTO rfcs (rfc, razon_social, regimen_fiscal) + VALUES ($1, $2, $3) + ON CONFLICT (rfc) DO UPDATE SET + razon_social = COALESCE(NULLIF($2, ''), rfcs.razon_social), + regimen_fiscal = CASE WHEN $3 IS NOT NULL AND $3 != '' THEN $3 ELSE rfcs.regimen_fiscal END + RETURNING id`, + [rfc, razonSocial || null, regimenFiscal || null] + ); + return rows[0].id; +} + +/** + * Guarda los XMLs extraídos del ZIP en disco para respaldo + */ +function saveXmlsToDisk( + zipBase64: string, + tenantRfc: string, + tipoCfdi: CfdiSyncType, + packageId: string +): string { + const baseDir = path.join(process.cwd(), 'data', 'xmls', tenantRfc.toLowerCase(), tipoCfdi); + fs.mkdirSync(baseDir, { recursive: true }); + + const xmlFiles = extractXmlsFromZip(zipBase64); + const packageDir = path.join(baseDir, packageId); + fs.mkdirSync(packageDir, { recursive: true }); + + for (const { filename, content } of xmlFiles) { + fs.writeFileSync(path.join(packageDir, filename), content, 'utf-8'); + } + + console.log(`[SAT] ${xmlFiles.length} XMLs guardados en ${packageDir}`); + return packageDir; +} + +/** + * Guarda los CFDIs en la base de datos del tenant + */ +async function saveCfdis( + pool: Pool, + cfdis: CfdiParsed[], + jobId: string, + contribuyenteId: string | null = null +): Promise<{ inserted: number; updated: number }> { + let inserted = 0; + let updated = 0; + + for (const cfdi of cfdis) { + try { + const tc = cfdi.tipoCambio || 1; + const m = (v: number) => v * tc; // compute MXN + const fechaEmision = cfdi.fechaEmision; + const year = String(fechaEmision.getFullYear()); + const month = String(fechaEmision.getMonth() + 1).padStart(2, '0'); + + // Upsert RFCs y obtener IDs + const rfcEmisorId = await getOrCreateRfc(pool, cfdi.rfcEmisor, cfdi.nombreEmisor, cfdi.regimenFiscalEmisor); + const rfcReceptorId = await getOrCreateRfc(pool, cfdi.rfcReceptor, cfdi.nombreReceptor, cfdi.regimenFiscalReceptor); + + // Normaliza UUID a lowercase (RFC 4122 canonical) para evitar duplicados + // entre el XML parser y el CSV metadata parser del SAT. + const uuidNorm = cfdi.uuid ? cfdi.uuid.toLowerCase() : cfdi.uuid; + + // All values for the full column set + const vals = [ + year, month, cfdi.type, uuidNorm, cfdi.serie, cfdi.folio, + cfdi.status, fechaEmision, + rfcEmisorId, cfdi.rfcEmisor, cfdi.nombreEmisor, + rfcReceptorId, cfdi.rfcReceptor, cfdi.nombreReceptor, + cfdi.subtotal, m(cfdi.subtotal), + cfdi.descuento, m(cfdi.descuento), + cfdi.total, m(cfdi.total), + cfdi.saldoInsoluto, + cfdi.moneda, tc, cfdi.tipoComprobante, + cfdi.metodoPago, cfdi.formaPago, cfdi.usoCfdi, + cfdi.pac, cfdi.fechaCertSat, + cfdi.uuidRelacionado, + cfdi.isrRetencion, m(cfdi.isrRetencion), + cfdi.ivaTraslado, m(cfdi.ivaTraslado), + cfdi.ivaRetencion, m(cfdi.ivaRetencion), + cfdi.iepsTraslado, m(cfdi.iepsTraslado), + cfdi.iepsRetencion, m(cfdi.iepsRetencion), + cfdi.impuestosLocalesTrasladado, m(cfdi.impuestosLocalesTrasladado), + cfdi.impuestosLocalesRetenidos, m(cfdi.impuestosLocalesRetenidos), + cfdi.montoPago, m(cfdi.montoPago), + cfdi.fechaPagoP, cfdi.numParcialidad, + cfdi.isrRetencionPago, m(cfdi.isrRetencionPago), + cfdi.ivaTrasladoPago, m(cfdi.ivaTrasladoPago), + cfdi.ivaRetencionPago, m(cfdi.ivaRetencionPago), + cfdi.iepsTrasladoPago, m(cfdi.iepsTrasladoPago), + cfdi.iepsRetencionPago, m(cfdi.iepsRetencionPago), + cfdi.fechaPago, cfdi.fechaInicialPago, cfdi.fechaFinalPago, + cfdi.numDiasPagados, cfdi.numSeguroSocial, cfdi.puesto, + cfdi.salarioBaseCotApor, m(cfdi.salarioBaseCotApor), + cfdi.salarioDiarioIntegrado, m(cfdi.salarioDiarioIntegrado), + cfdi.totalPercepciones, m(cfdi.totalPercepciones), + cfdi.totalDeducciones, m(cfdi.totalDeducciones), + cfdi.impRetenidosNomina, m(cfdi.impRetenidosNomina), + cfdi.otrasDeduccionesNomina, m(cfdi.otrasDeduccionesNomina), + cfdi.subsidioCausado, m(cfdi.subsidioCausado), + cfdi.regimenFiscalEmisor, cfdi.regimenFiscalReceptor, + cfdi.xmlOriginal, + cfdi.cfdiTipoRelacion, cfdi.cfdisRelacionados, + jobId, + ]; + + const { rows: existing } = await pool.query( + `SELECT id FROM cfdis WHERE LOWER(uuid) = $1`, + [uuidNorm] + ); + + if (existing.length > 0) { + // $1=uuid(WHERE), $2-$85=all vals (includes rfc_emisor_id, rfc_receptor_id, + // cfdi_tipo_relacion, cfdis_relacionados) + await pool.query( + `UPDATE cfdis SET + year=$2, month=$3, type=$4, uuid=$5, serie=$6, folio=$7, + status=$8, fecha_emision=$9, + rfc_emisor_id=$10, rfc_emisor=$11, nombre_emisor=$12, + rfc_receptor_id=$13, rfc_receptor=$14, nombre_receptor=$15, + subtotal=$16, subtotal_mxn=$17, descuento=$18, descuento_mxn=$19, + total=$20, total_mxn=$21, saldo_insoluto=$22, + moneda=$23, tipo_cambio=$24, tipo_comprobante=$25, + metodo_pago=$26, forma_pago=$27, uso_cfdi=$28, + pac=$29, fecha_cert_sat=$30, uuid_relacionado=$31, + isr_retencion=$32, isr_retencion_mxn=$33, + iva_traslado=$34, iva_traslado_mxn=$35, + iva_retencion=$36, iva_retencion_mxn=$37, + ieps_traslado=$38, ieps_traslado_mxn=$39, + ieps_retencion=$40, ieps_retencion_mxn=$41, + impuestos_locales_trasladado=$42, impuestos_locales_trasladado_mxn=$43, + impuestos_locales_retenidos=$44, impuestos_locales_retenidos_mxn=$45, + monto_pago=$46, monto_pago_mxn=$47, + fecha_pago_p=$48, num_parcialidad=$49, + isr_retencion_pago=$50, isr_retencion_pago_mxn=$51, + iva_traslado_pago=$52, iva_traslado_pago_mxn=$53, + iva_retencion_pago=$54, iva_retencion_pago_mxn=$55, + ieps_traslado_pago=$56, ieps_traslado_pago_mxn=$57, + ieps_retencion_pago=$58, ieps_retencion_pago_mxn=$59, + fecha_pago=$60, fecha_inicial_pago=$61, fecha_final_pago=$62, + num_dias_pagados=$63, num_seguro_social=$64, puesto=$65, + salario_base_cot_apor=$66, salario_base_cot_apor_mxn=$67, + salario_diario_integrado=$68, salario_diario_integrado_mxn=$69, + total_percepciones=$70, total_percepciones_mxn=$71, + total_deducciones=$72, total_deducciones_mxn=$73, + imp_retenidos_nomina=$74, imp_retenidos_nomina_mxn=$75, + otras_deducciones_nomina=$76, otras_deducciones_nomina_mxn=$77, + subsidio_causado=$78, subsidio_causado_mxn=$79, + regimen_fiscal_emisor=$80, regimen_fiscal_receptor=$81, + xml_original=$82, + cfdi_tipo_relacion=$83, cfdis_relacionados=$84, + last_sat_sync=NOW(), sat_sync_job_id=$85::uuid, + actualizado_en=NOW() + WHERE uuid = $1`, + [cfdi.uuid, ...vals] + ); + // Re-insert conceptos for updated CFDI + await pool.query(`DELETE FROM cfdi_conceptos WHERE cfdi_id = $1`, [existing[0].id]); + await saveConceptos(pool, existing[0].id, cfdi); + updated++; + } else { + // $1-$83 = data fields (year..cfdis_relacionados), $84 = jobId, $85 = contribuyente_id + const dataPlaceholders = vals.slice(0, -1).map((_, i) => `$${i + 1}`).join(','); + await pool.query( + `INSERT INTO cfdis ( + year, month, type, uuid, serie, folio, status, fecha_emision, + rfc_emisor_id, rfc_emisor, nombre_emisor, + rfc_receptor_id, rfc_receptor, nombre_receptor, + subtotal, subtotal_mxn, descuento, descuento_mxn, + total, total_mxn, saldo_insoluto, + moneda, tipo_cambio, tipo_comprobante, + metodo_pago, forma_pago, uso_cfdi, + pac, fecha_cert_sat, uuid_relacionado, + isr_retencion, isr_retencion_mxn, + iva_traslado, iva_traslado_mxn, + iva_retencion, iva_retencion_mxn, + ieps_traslado, ieps_traslado_mxn, + ieps_retencion, ieps_retencion_mxn, + impuestos_locales_trasladado, impuestos_locales_trasladado_mxn, + impuestos_locales_retenidos, impuestos_locales_retenidos_mxn, + monto_pago, monto_pago_mxn, + fecha_pago_p, num_parcialidad, + isr_retencion_pago, isr_retencion_pago_mxn, + iva_traslado_pago, iva_traslado_pago_mxn, + iva_retencion_pago, iva_retencion_pago_mxn, + ieps_traslado_pago, ieps_traslado_pago_mxn, + ieps_retencion_pago, ieps_retencion_pago_mxn, + fecha_pago, fecha_inicial_pago, fecha_final_pago, + num_dias_pagados, num_seguro_social, puesto, + salario_base_cot_apor, salario_base_cot_apor_mxn, + salario_diario_integrado, salario_diario_integrado_mxn, + total_percepciones, total_percepciones_mxn, + total_deducciones, total_deducciones_mxn, + imp_retenidos_nomina, imp_retenidos_nomina_mxn, + otras_deducciones_nomina, otras_deducciones_nomina_mxn, + subsidio_causado, subsidio_causado_mxn, + regimen_fiscal_emisor, regimen_fiscal_receptor, + xml_original, + cfdi_tipo_relacion, cfdis_relacionados, + source, sat_sync_job_id, last_sat_sync, contribuyente_id + ) VALUES ( + ${dataPlaceholders}, + 'sat', $${vals.length}::uuid, NOW(), $${vals.length + 1} + )`, + [...vals, contribuyenteId] + ); + // Get the inserted cfdi id and save conceptos + const { rows: [newRow] } = await pool.query(`SELECT id FROM cfdis WHERE uuid = $1`, [cfdi.uuid]); + if (newRow) await saveConceptos(pool, newRow.id, cfdi); + inserted++; + } + // Marcar el mes para recompute de métricas pre-calculadas. Para tipo P + // el mes contable es el de fecha_pago_p (coherente con los cálculos + // fiscales). Solo tiene sentido en tenants con contribuyentes. + if (contribuyenteId) { + const fechaContableRaw = cfdi.tipoComprobante === 'P' && cfdi.fechaPagoP + ? cfdi.fechaPagoP + : cfdi.fechaEmision; + const fechaContable = fechaContableRaw instanceof Date ? fechaContableRaw : new Date(fechaContableRaw); + const anio = fechaContable.getFullYear(); + const mes = fechaContable.getMonth() + 1; + await markForInvalidation(pool, contribuyenteId, anio, mes, 'SAT_SYNC_CFDI').catch( + err => console.warn('[SAT] markForInvalidation falló:', err?.message || err), + ); + } + } catch (error) { + console.error(`[SAT] Error guardando CFDI ${cfdi.uuid}:`, error); + } + } + + // Recompute saldo_pendiente_mxn para todos los CFDIs afectados por este + // batch: los I PPD recién insertados, más los I PPD referenciados por los + // P y E no-07 que entraron. Un solo UPDATE agregado al final del loop es + // más eficiente que uno por CFDI (la subquery del SALDO es costosa). + const afectados = new Set(); + for (const cfdi of cfdis) { + for (const u of uuidsAfectadosPorCfdi(cfdi)) afectados.add(u); + } + if (afectados.size > 0) { + try { + await recomputarSaldoPendiente(pool, Array.from(afectados)); + } catch (err: any) { + console.warn(`[SAT] recomputarSaldoPendiente falló: ${err?.message || err}`); + } + } + + return { inserted, updated }; +} + +/** + * Guarda los conceptos de un CFDI en cfdi_conceptos + */ +async function saveConceptos(pool: Pool, cfdiId: number, cfdi: CfdiParsed): Promise { + if (!cfdi.conceptos || cfdi.conceptos.length === 0) return; + + const tc = cfdi.tipoCambio || 1; + const m = (v: number) => v * tc; + + for (const c of cfdi.conceptos) { + await pool.query(` + INSERT INTO cfdi_conceptos ( + cfdi_id, + clave_prod_serv, no_identificacion, descripcion, cantidad, + clave_unidad, unidad, + valor_unitario, valor_unitario_mxn, importe, importe_mxn, + descuento, descuento_mxn, + isr_retencion, isr_retencion_mxn, + iva_traslado, iva_traslado_mxn, + iva_retencion, iva_retencion_mxn, + ieps_traslado, ieps_traslado_mxn, + ieps_retencion, ieps_retencion_mxn + ) VALUES ( + $1,$2,$3,$4,$5,$6,$7,$8,$9,$10, + $11,$12,$13,$14,$15,$16,$17,$18,$19,$20, + $21,$22,$23 + ) + `, [ + cfdiId, + c.claveProdServ, c.noIdentificacion, c.descripcion, c.cantidad, + c.claveUnidad, c.unidad, + c.valorUnitario, m(c.valorUnitario), c.importe, m(c.importe), + c.descuento, m(c.descuento), + c.isrRetencion, m(c.isrRetencion), + c.ivaTraslado, m(c.ivaTraslado), + c.ivaRetencion, m(c.ivaRetencion), + c.iepsTraslado, m(c.iepsTraslado), + c.iepsRetencion, m(c.iepsRetencion), + ]); + } +} + +/** + * Guarda/actualiza CFDIs desde metadata del SAT. + * - Si el CFDI no existe: inserta con datos básicos de metadata (sin XML). + * - Si el CFDI ya existe y la metadata dice Cancelado: actualiza status + fecha_cancelacion. + * - Si el CFDI ya existe y sigue Vigente: no toca nada (los datos del XML son más completos). + */ +async function saveMetadata( + pool: Pool, + items: CfdiMetadata[], + jobId: string, + contribuyenteId: string | null = null +): Promise<{ inserted: number; updated: number }> { + let inserted = 0; + let updated = 0; + + for (const m of items) { + try { + // Normaliza UUID a lowercase para evitar duplicados case-sensitive con + // el XML parser (que también normaliza). CSV del SAT lo devuelve UPPERCASE. + const uuidNorm = m.uuid ? m.uuid.toLowerCase() : m.uuid; + + const { rows: existing } = await pool.query( + `SELECT id, status FROM cfdis WHERE LOWER(uuid) = $1`, + [uuidNorm] + ); + + if (existing.length > 0) { + // Solo actualizar si cambió a Cancelado + if (m.status === 'Cancelado' && existing[0].status !== 'Cancelado') { + await pool.query( + `UPDATE cfdis SET status = 'Cancelado', fecha_cancelacion = $2, actualizado_en = NOW() + WHERE id = $1`, + [existing[0].id, m.fechaCancelacion] + ); + updated++; + } + } else { + // Insertar CFDI con datos básicos de metadata (sin XML) + const year = String(m.fechaEmision.getFullYear()); + const month = String(m.fechaEmision.getMonth() + 1).padStart(2, '0'); + + // Upsert RFCs + const rfcEmisorId = await getOrCreateRfc(pool, m.rfcEmisor, m.nombreEmisor); + const rfcReceptorId = await getOrCreateRfc(pool, m.rfcReceptor, m.nombreReceptor); + + await pool.query( + `INSERT INTO cfdis ( + year, month, type, uuid, status, fecha_emision, fecha_cancelacion, + rfc_emisor_id, rfc_emisor, nombre_emisor, + rfc_receptor_id, rfc_receptor, nombre_receptor, + total, total_mxn, moneda, tipo_comprobante, + pac, fecha_cert_sat, + source, sat_sync_job_id, last_sat_sync, contribuyente_id + ) VALUES ( + $1, $2, $3, $4, $5, $6, $7, + $8, $9, $10, + $11, $12, $13, + $14, $14, 'MXN', $15, + $16, $17, + 'sat-metadata', $18::uuid, NOW(), $19 + )`, + [ + year, month, m.type, uuidNorm, m.status, m.fechaEmision, m.fechaCancelacion, + rfcEmisorId, m.rfcEmisor, m.nombreEmisor, + rfcReceptorId, m.rfcReceptor, m.nombreReceptor, + m.monto, m.tipoComprobante, + m.rfcPac, m.fechaCertSat, + jobId, contribuyenteId, + ] + ); + inserted++; + } + // Invalidar metricas: tanto insert como status→Cancelado afectan el mes. + if (contribuyenteId) { + const anio = m.fechaEmision.getFullYear(); + const mes = m.fechaEmision.getMonth() + 1; + await markForInvalidation(pool, contribuyenteId, anio, mes, 'SAT_METADATA').catch( + err => console.warn('[SAT] markForInvalidation falló:', err?.message || err), + ); + } + } catch (error: any) { + console.error(`[SAT] Error guardando metadata CFDI ${m.uuid}:`, error.message); + } + } + + return { inserted, updated }; +} + +/** + * Construye el identificador único de un request dentro de un job. + * Hay 2-N requests por job (daily=4, initial=N×4) — necesitamos distinguirlos + * para reusar el correcto en retries. + */ +function makeRequestKindKey( + fechaInicio: Date, + fechaFin: Date, + tipoCfdi: CfdiSyncType, + requestType: 'cfdi' | 'metadata', +): string { + return `${requestType}-${tipoCfdi}-${fechaInicio.toISOString().slice(0, 10)}-${fechaFin.toISOString().slice(0, 10)}`; +} + +/** + * Persiste un (kindKey, requestId) en el mapa `sat_request_ids` con merge + * atómico SQL. Evita race conditions vs read-modify-write desde JS. + * Sigue actualizando `satRequestId` (singular) para backward compat. + */ +async function persistSatRequestId(jobId: string, kindKey: string, requestId: string): Promise { + await prisma.$executeRawUnsafe( + `UPDATE sat_sync_jobs + SET sat_request_ids = COALESCE(sat_request_ids, '{}'::jsonb) || $1::jsonb, + sat_request_id = $2 + WHERE id = $3`, + JSON.stringify({ [kindKey]: requestId }), + requestId, + jobId, + ); +} + +/** + * Solicita, espera y descarga paquetes del SAT para un rango+tipo+requestType. + * Retorna los contenidos base64 de los paquetes descargados. + * + * Reuso de requestIds en retries: + * - Antes de crear una nueva solicitud al SAT, busca si el job ya tiene un + * requestId guardado para esta misma kindKey (mismo rango+tipo+requestType). + * - Si existe: hace `verifySatRequest` directo. Si está listo → descarga. + * Si está pending/processing → entra al polling con ese mismo id. + * Si está failed/rejected o el verify lanza excepción → fallback a crear nuevo. + * - Esto evita quemar cuota del SAT en cada reintento (límite ~5 solicitudes + * activas por RFC). + */ +async function requestAndDownload( + ctx: SyncContext, + jobId: string, + fechaInicio: Date, + fechaFin: Date, + tipoCfdi: CfdiSyncType, + requestType: 'cfdi' | 'metadata', +): Promise<{ packageContents: string[]; totalCfdis: number }> { + const label = `${tipoCfdi}/${requestType}`; + const kindKey = makeRequestKindKey(fechaInicio, fechaFin, tipoCfdi, requestType); + + // Intentar reusar requestId previo del mismo job/kindKey (caso retry) + const jobRow = await prisma.satSyncJob.findUnique({ + where: { id: jobId }, + select: { satRequestIds: true }, + }); + const existingMap = (jobRow?.satRequestIds as Record | null) || {}; + let requestId: string | null = existingMap[kindKey] || null; + let verifyResult: Awaited> | undefined; + + if (requestId) { + console.log(`[SAT] Reusando requestId previo (${label}): ${requestId}`); + try { + verifyResult = await verifySatRequest(ctx.service, requestId); + console.log(`[SAT] Estado del request reusado (${label}): ${verifyResult.status}`); + + // Estados terminales inválidos → descartar y crear nuevo + if (verifyResult.status === 'failed' || verifyResult.status === 'rejected') { + console.log(`[SAT] Request reusado en estado ${verifyResult.status}, creando nuevo`); + requestId = null; + verifyResult = undefined; + } + } catch (err: any) { + // El SAT a veces devuelve errores raros para requestIds expirados (>72h). + // Defensivo: descartar y crear nuevo. + console.warn(`[SAT] Verify del request reusado falló (${err.message}), creando nuevo`); + requestId = null; + verifyResult = undefined; + } + } + + // Si no hay requestId válido reusable, crear nuevo + if (!requestId) { + console.log(`[SAT] Solicitando ${label} desde ${fechaInicio.toISOString()} hasta ${fechaFin.toISOString()}`); + + const queryResult = await querySat(ctx.service, fechaInicio, fechaFin, tipoCfdi, requestType); + + if (!queryResult.success) { + if (queryResult.statusCode === '5004') { + console.log(`[SAT] No se encontraron CFDIs (${label})`); + return { packageContents: [], totalCfdis: 0 }; + } + throw new Error(`Error SAT (${label}): ${queryResult.message}`); + } + + requestId = queryResult.requestId!; + console.log(`[SAT] Nueva solicitud creada (${label}): ${requestId}`); + + await persistSatRequestId(jobId, kindKey, requestId); + } + + // Polling — si el reuse ya devolvió `ready`, salta el loop directamente. + if (!verifyResult || verifyResult.status !== 'ready') { + let attempts = 0; + while (attempts < MAX_POLL_ATTEMPTS) { + await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS)); + attempts++; + + verifyResult = await verifySatRequest(ctx.service, requestId); + console.log(`[SAT] Estado ${label}: ${verifyResult.status} (intento ${attempts})`); + + if (verifyResult.status === 'ready') break; + if (verifyResult.status === 'failed' || verifyResult.status === 'rejected') { + throw new Error(`Solicitud fallida (${label}): ${verifyResult.message}`); + } + } + } + + if (!verifyResult || verifyResult.status !== 'ready') { + throw new Error(`Timeout esperando respuesta del SAT (${label})`); + } + + const packageContents: string[] = []; + for (let i = 0; i < verifyResult.packageIds.length; i++) { + const packageId = verifyResult.packageIds[i]; + console.log(`[SAT] Descargando paquete ${label} ${i + 1}/${verifyResult.packageIds.length}: ${packageId}`); + + const downloadResult = await downloadSatPackage(ctx.service, packageId); + if (!downloadResult.success) { + console.error(`[SAT] Error descargando paquete ${packageId}: ${downloadResult.message}`); + continue; + } + + // Guardar en disco como respaldo + if (requestType === 'cfdi') { + try { + saveXmlsToDisk(downloadResult.packageContent, ctx.rfc, tipoCfdi, packageId); + } catch (err: any) { + console.error(`[SAT] Error guardando XMLs en disco: ${err.message}`); + } + } + + packageContents.push(downloadResult.packageContent); + } + + return { packageContents, totalCfdis: verifyResult.totalCfdis }; +} + +/** + * Procesa una solicitud de descarga para un rango de fechas. + * 1) Descarga XMLs de CFDIs vigentes → INSERT/UPDATE completo + * 2) Descarga metadata de todos (vigentes+cancelados) → INSERT básico o UPDATE status + */ +async function processDateRange( + ctx: SyncContext, + jobId: string, + fechaInicio: Date, + fechaFin: Date, + tipoCfdi: CfdiSyncType +): Promise<{ found: number; downloaded: number; inserted: number; updated: number }> { + let totalFound = 0; + let totalDownloaded = 0; + let totalInserted = 0; + let totalUpdated = 0; + + // Solo XMLs de vigentes (datos completos) + try { + const { packageContents, totalCfdis } = await requestAndDownload( + ctx, jobId, fechaInicio, fechaFin, tipoCfdi, 'cfdi' + ); + totalFound += totalCfdis; + + for (const content of packageContents) { + const cfdis = processPackage(content, tipoCfdi); + totalDownloaded += cfdis.length; + console.log(`[SAT] Procesando ${cfdis.length} CFDIs XML del paquete`); + + const { inserted, updated } = await saveCfdis(await ctx.getPool(), cfdis, jobId, ctx.contribuyenteId); + totalInserted += inserted; + totalUpdated += updated; + } + } catch (error: any) { + console.error(`[SAT] Error en XMLs ${tipoCfdi}: ${error.message}`); + } + + await updateJobProgress(jobId, { + cfdisFound: totalFound, + cfdisDownloaded: totalDownloaded, + cfdisInserted: totalInserted, + cfdisUpdated: totalUpdated, + }); + + return { + found: totalFound, + downloaded: totalDownloaded, + inserted: totalInserted, + updated: totalUpdated, + }; +} + +/** + * Descarga y procesa metadata de un rango de fechas para un tipo de CFDI. + * Metadata incluye vigentes + cancelados. + */ +async function processMetadataRange( + ctx: SyncContext, + jobId: string, + fechaInicio: Date, + fechaFin: Date, + tipoCfdi: CfdiSyncType +): Promise<{ inserted: number; updated: number }> { + let totalInserted = 0; + let totalUpdated = 0; + + try { + const { packageContents } = await requestAndDownload( + ctx, jobId, fechaInicio, fechaFin, tipoCfdi, 'metadata' + ); + + for (const content of packageContents) { + const items = processMetadataPackage(content, tipoCfdi); + console.log(`[SAT] Procesando ${items.length} registros de metadata ${tipoCfdi}`); + + const { inserted, updated } = await saveMetadata(await ctx.getPool(), items, jobId, ctx.contribuyenteId); + totalInserted += inserted; + totalUpdated += updated; + } + } catch (error: any) { + console.error(`[SAT] Error en metadata ${tipoCfdi}: ${error.message}`); + } + + return { inserted: totalInserted, updated: totalUpdated }; +} + +/** + * Determina el tamaño de bloque óptimo consultando metadata del rango completo. + * <= 15,000 CFDIs → bloques de 6 meses + * > 15,000 CFDIs → bloques de 2 meses + */ +async function determineChunkMonths( + ctx: SyncContext, + jobId: string, + fechaInicio: Date, + fechaFin: Date, +): Promise { + const THRESHOLD = 15_000; + let totalCfdis = 0; + + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const { totalCfdis: count } = await requestAndDownload( + ctx, jobId, fechaInicio, fechaFin, tipo, 'metadata' + ); + totalCfdis += count; + console.log(`[SAT] Sondeo metadata ${tipo}: ${count} CFDIs en rango completo`); + } catch (error: any) { + console.log(`[SAT] No se pudo sondear metadata ${tipo}: ${error.message}`); + } + } + + const chunkMonths = totalCfdis > THRESHOLD ? 3 : 6; + console.log(`[SAT] Total estimado: ${totalCfdis} CFDIs → bloques de ${chunkMonths} meses`); + return chunkMonths; +} + +/** + * Genera bloques de fechas segmentados por N meses. + */ +function generateChunks(fechaInicio: Date, fechaFin: Date, chunkMonths: number): { start: Date; end: Date }[] { + const chunks: { start: Date; end: Date }[] = []; + let current = new Date(fechaInicio); + + while (current < fechaFin) { + const chunkEnd = new Date(current.getFullYear(), current.getMonth() + chunkMonths, 0, 23, 59, 59); + const end = chunkEnd > fechaFin ? fechaFin : chunkEnd; + chunks.push({ start: new Date(current), end }); + current = new Date(current.getFullYear(), current.getMonth() + chunkMonths, 1); + } + + return chunks; +} + +/** + * Ejecuta sincronización inicial o por rango personalizado. + * - XMLs: bloques de 3 o 6 meses (según volumen) + * - Metadata: bloques anuales (siempre) + */ +async function processInitialSync( + ctx: SyncContext, + jobId: string, + customDateFrom?: Date, + customDateTo?: Date +): Promise { + const ahora = new Date(); + const inicioHistorico = customDateFrom || new Date(ahora.getFullYear() - YEARS_TO_SYNC, ahora.getMonth(), 1); + const fechaFin = customDateTo || ahora; + + // Paso 1: Sondeo — determinar tamaño de bloque para XMLs + const chunkMonths = await determineChunkMonths(ctx, jobId, inicioHistorico, fechaFin); + const xmlChunks = generateChunks(inicioHistorico, fechaFin, chunkMonths); + const metaChunks = generateChunks(inicioHistorico, fechaFin, 36); // bloques de 3 años + + console.log(`[SAT] Sincronización: ${xmlChunks.length} bloques XML (${chunkMonths}m) + ${metaChunks.length} bloques metadata (36m)`); + + let totalFound = 0; + let totalDownloaded = 0; + let totalInserted = 0; + let totalUpdated = 0; + + // Paso 2: Descargar XMLs de vigentes (bloques de 3/6 meses) + for (let i = 0; i < xmlChunks.length; i++) { + const { start, end } = xmlChunks[i]; + console.log(`[SAT] XML bloque ${i + 1}/${xmlChunks.length}: ${start.toISOString().slice(0, 10)} → ${end.toISOString().slice(0, 10)}`); + + try { + const emitidos = await processDateRange(ctx, jobId, start, end, 'emitidos'); + totalFound += emitidos.found; + totalDownloaded += emitidos.downloaded; + totalInserted += emitidos.inserted; + totalUpdated += emitidos.updated; + } catch (error: any) { + console.error(`[SAT] Error emitidos XML bloque ${i + 1}:`, error.message); + } + + try { + const recibidos = await processDateRange(ctx, jobId, start, end, 'recibidos'); + totalFound += recibidos.found; + totalDownloaded += recibidos.downloaded; + totalInserted += recibidos.inserted; + totalUpdated += recibidos.updated; + } catch (error: any) { + console.error(`[SAT] Error recibidos XML bloque ${i + 1}:`, error.message); + } + + await new Promise(resolve => setTimeout(resolve, 5000)); + } + + // Paso 3: Descargar metadata (bloques anuales) + for (let i = 0; i < metaChunks.length; i++) { + const { start, end } = metaChunks[i]; + console.log(`[SAT] Metadata bloque ${i + 1}/${metaChunks.length}: ${start.toISOString().slice(0, 10)} → ${end.toISOString().slice(0, 10)}`); + + try { + const { inserted, updated } = await processMetadataRange(ctx, jobId, start, end, 'emitidos'); + totalInserted += inserted; + totalUpdated += updated; + } catch (error: any) { + console.error(`[SAT] Error metadata emitidos bloque ${i + 1}:`, error.message); + } + + try { + const { inserted, updated } = await processMetadataRange(ctx, jobId, start, end, 'recibidos'); + totalInserted += inserted; + totalUpdated += updated; + } catch (error: any) { + console.error(`[SAT] Error metadata recibidos bloque ${i + 1}:`, error.message); + } + + await new Promise(resolve => setTimeout(resolve, 5000)); + } + + await updateJobProgress(jobId, { + cfdisFound: totalFound, + cfdisDownloaded: totalDownloaded, + cfdisInserted: totalInserted, + cfdisUpdated: totalUpdated, + }); +} + +/** + * Ejecuta sincronización diaria (mes actual) + */ +/** + * Procesa un rango personalizado de fechas. + * Si el rango supera 6 meses, divide en bloques de 6 meses. + * Si no, usa el rango directamente. + */ +async function processCustomRangeSync( + ctx: SyncContext, + jobId: string, + dateFrom: Date, + dateTo: Date +): Promise { + const diffMs = dateTo.getTime() - dateFrom.getTime(); + const diffMonths = diffMs / (1000 * 60 * 60 * 24 * 30); + const MAX_MONTHS_DIRECT = 6; + + let totalFound = 0; + let totalDownloaded = 0; + let totalInserted = 0; + let totalUpdated = 0; + + if (diffMonths <= MAX_MONTHS_DIRECT) { + // Rango <= 6 meses: solicitud directa sin dividir + console.log(`[SAT] Rango personalizado (${diffMonths.toFixed(1)} meses) — solicitud directa`); + + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const result = await processDateRange(ctx, jobId, dateFrom, dateTo, tipo); + totalFound += result.found; + totalDownloaded += result.downloaded; + totalInserted += result.inserted; + totalUpdated += result.updated; + } catch (error: any) { + console.error(`[SAT] Error ${tipo} rango personalizado:`, error.message); + } + } + } else { + // Rango > 6 meses: dividir en bloques de 6 meses + const chunks = generateChunks(dateFrom, dateTo, 6); + console.log(`[SAT] Rango personalizado (${diffMonths.toFixed(1)} meses) — ${chunks.length} bloques de 6 meses`); + + for (let i = 0; i < chunks.length; i++) { + const { start, end } = chunks[i]; + console.log(`[SAT] Bloque ${i + 1}/${chunks.length}: ${start.toISOString().slice(0, 10)} → ${end.toISOString().slice(0, 10)}`); + + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const result = await processDateRange(ctx, jobId, start, end, tipo); + totalFound += result.found; + totalDownloaded += result.downloaded; + totalInserted += result.inserted; + totalUpdated += result.updated; + } catch (error: any) { + console.error(`[SAT] Error ${tipo} bloque ${i + 1}:`, error.message); + } + } + + // Pausa entre bloques para no saturar el SAT + if (i < chunks.length - 1) { + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } + } + + // Metadata: siempre el rango completo (incluye cancelados) + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const { inserted, updated } = await processMetadataRange(ctx, jobId, dateFrom, dateTo, tipo); + totalInserted += inserted; + totalUpdated += updated; + } catch (error: any) { + console.error(`[SAT] Error metadata ${tipo} rango personalizado:`, error.message); + } + } + + await updateJobProgress(jobId, { + cfdisFound: totalFound, + cfdisDownloaded: totalDownloaded, + cfdisInserted: totalInserted, + cfdisUpdated: totalUpdated, + }); +} + +/** + * Sincronización incremental: ventana fija de las últimas 8 horas. + * Diseñada para correr 3 veces al día (11:00, 15:00, 19:00) en clientes Enterprise. + * La ventana de 8h cubre el gap máximo (03:00 → 11:00) entre el daily y el primer + * incremental; los disparos siguientes solapan, pero la unicidad del UUID deduplica. + * Fuera de ese rango, el daily de 03:00 se encarga. + */ +const INCREMENTAL_WINDOW_HOURS = 8; + +async function processIncrementalSync(ctx: SyncContext, jobId: string): Promise { + const ahora = new Date(); + const desde = new Date(ahora.getTime() - INCREMENTAL_WINDOW_HOURS * 60 * 60 * 1000); + + let totalFound = 0; + let totalDownloaded = 0; + let totalInserted = 0; + let totalUpdated = 0; + + console.log(`[SAT] Incremental: ${desde.toISOString()} → ${ahora.toISOString()} (${INCREMENTAL_WINDOW_HOURS}h)`); + + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const result = await processDateRange(ctx, jobId, desde, ahora, tipo); + totalFound += result.found; + totalDownloaded += result.downloaded; + totalInserted += result.inserted; + totalUpdated += result.updated; + } catch (error: any) { + console.error(`[SAT] Error incremental XMLs ${tipo}:`, error.message); + } + } + + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const { inserted, updated } = await processMetadataRange(ctx, jobId, desde, ahora, tipo); + totalInserted += inserted; + totalUpdated += updated; + } catch (error: any) { + console.error(`[SAT] Error incremental metadata ${tipo}:`, error.message); + } + } + + await updateJobProgress(jobId, { + cfdisFound: totalFound, + cfdisDownloaded: totalDownloaded, + cfdisInserted: totalInserted, + cfdisUpdated: totalUpdated, + }); +} + +async function processDailySync(ctx: SyncContext, jobId: string): Promise { + const ahora = new Date(); + const inicioAño = new Date(ahora.getFullYear(), 0, 1); + const hace7Dias = new Date(ahora.getTime() - 7 * 24 * 60 * 60 * 1000); + + let totalFound = 0; + let totalDownloaded = 0; + let totalInserted = 0; + let totalUpdated = 0; + + // Paso 1: XMLs de los últimos 7 días (CFDIs nuevos) + console.log(`[SAT] Daily: XMLs desde ${hace7Dias.toISOString().slice(0, 10)} → ${ahora.toISOString().slice(0, 10)}`); + + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const result = await processDateRange(ctx, jobId, hace7Dias, ahora, tipo); + totalFound += result.found; + totalDownloaded += result.downloaded; + totalInserted += result.inserted; + totalUpdated += result.updated; + } catch (error: any) { + console.error(`[SAT] Error XMLs ${tipo} (7 días):`, error.message); + } + } + + // Paso 2: Metadata del ciclo fiscal actual (enero → hoy) + // Captura cancelaciones y cambios de status del año completo + console.log(`[SAT] Daily: Metadata desde ${inicioAño.toISOString().slice(0, 10)} → ${ahora.toISOString().slice(0, 10)}`); + + for (const tipo of ['emitidos', 'recibidos'] as const) { + try { + const { inserted, updated } = await processMetadataRange(ctx, jobId, inicioAño, ahora, tipo); + totalInserted += inserted; + totalUpdated += updated; + } catch (error: any) { + console.error(`[SAT] Error metadata ${tipo} (ciclo fiscal):`, error.message); + } + } + + await updateJobProgress(jobId, { + cfdisFound: totalFound, + cfdisDownloaded: totalDownloaded, + cfdisInserted: totalInserted, + cfdisUpdated: totalUpdated, + }); +} + +/** + * Inicia la sincronización con el SAT + */ +export async function startSync( + tenantId: string, + type: SatSyncType = 'daily', + dateFrom?: Date, + dateTo?: Date, + contribuyenteId?: string +): Promise { + // Try per-contribuyente FIEL first (despachos), then legacy (Horux360) + let decryptedFiel = null; + if (contribuyenteId) { + const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { databaseName: true } }); + if (tenant) { + const pool = await tenantDb.getPool(tenantId, tenant.databaseName); + decryptedFiel = await getDecryptedFielContribuyente(pool, contribuyenteId); + } + } + if (!decryptedFiel) { + decryptedFiel = await getDecryptedFiel(tenantId); + } + if (!decryptedFiel) { + throw new Error('No hay FIEL configurada o está vencida'); + } + + const fielData: FielData = { + cerContent: decryptedFiel.cerContent, + keyContent: decryptedFiel.keyContent, + password: decryptedFiel.password, + }; + + const service = createSatService(fielData); + + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { databaseName: true }, + }); + + if (!tenant) { + throw new Error('Tenant no encontrado'); + } + + // Lock a nivel (tenantId, contribuyenteId). Contribuyentes distintos dentro + // del mismo tenant (despacho) pueden sincronizarse en paralelo — cada uno + // usa su propio FIEL y su propio conjunto de CFDIs. El null de Horux 360 + // (tenant-wide) solo se bloquea contra sí mismo. + const activeSync = await prisma.satSyncJob.findFirst({ + where: { + tenantId, + contribuyenteId: contribuyenteId ?? null, + status: { in: ['pending', 'running'] }, + }, + }); + + if (activeSync) { + throw new Error('Ya hay una sincronización en curso'); + } + + const now = new Date(); + // `isCustomRange` solo aplica a 'initial' con fechas explícitas del UI. + // Bootstrap puro = initial sin fechas → usa default de 6 años atrás. + const isCustomRange = type === 'initial' && (!!dateFrom || !!dateTo); + const job = await prisma.satSyncJob.create({ + data: { + tenantId, + contribuyenteId: contribuyenteId || null, + type, + status: 'running', + dateFrom: dateFrom || new Date(now.getFullYear() - YEARS_TO_SYNC, 0, 1), + dateTo: dateTo || now, + startedAt: now, + isCustomRange, + }, + }); + + const ctx: SyncContext = { + fielData, + service, + rfc: decryptedFiel.rfc, + tenantId, + databaseName: tenant.databaseName, + contribuyenteId: contribuyenteId || null, + getPool: () => tenantDb.getPool(tenantId, tenant.databaseName), + }; + + // Ejecutar sincronización en background + (async () => { + try { + if (type === 'initial') { + await processInitialSync(ctx, job.id, dateFrom, dateTo); + } else if (type === 'incremental') { + await processIncrementalSync(ctx, job.id); + } else if (dateFrom && dateTo) { + await processCustomRangeSync(ctx, job.id, dateFrom, dateTo); + } else { + await processDailySync(ctx, job.id); + } + + await updateJobProgress(job.id, { + status: 'completed', + completedAt: new Date(), + progressPercent: 100, + }); + + console.log(`[SAT] Sincronización ${job.id} completada`); + } catch (error: any) { + console.error(`[SAT] Error en sincronización ${job.id}:`, error); + + const isTimeout = error.message?.includes('Timeout'); + const currentRetries = job.retryCount || 0; + const policy = getRetryPolicy(job); + const nextRetryNumber = currentRetries + 1; + const nextRetry = isTimeout && nextRetryNumber <= policy.maxRetries + ? computeNextRetryAt(job.startedAt!, nextRetryNumber, policy) + : null; + + if (nextRetry) { + await updateJobProgress(job.id, { + status: 'pending', + errorMessage: `Timeout (intento ${nextRetryNumber}/${policy.maxRetries}). Reintento programado para ${nextRetry.toLocaleString('es-MX')}.`, + retryCount: nextRetryNumber, + nextRetryAt: nextRetry, + }); + console.log(`[SAT] Job ${job.id} programado para reintento ${nextRetryNumber}/${policy.maxRetries} a las ${nextRetry.toLocaleString('es-MX')}`); + } else { + // Sin reintentos restantes, error no-timeout, o policy con maxRetries=0 (incremental) + const finalMsg = isTimeout + ? policy.maxRetries === 0 + ? 'Timeout en sync incremental — sin reintentos por política. Próximo cron incremental cubrirá el gap.' + : 'Fallo conexión SAT, vuelve a intentar con un rango de fechas menor.' + : error.message; + await updateJobProgress(job.id, { + status: 'failed', + errorMessage: finalMsg, + completedAt: new Date(), + }); + } + } + })(); + + return job.id; +} + +/** + * Reintenta jobs de SAT que tienen nextRetryAt pasado. + * Llamado por el cron cada hora. + */ +export async function retryTimedOutJobs(): Promise { + // No filtramos por retryCount aquí porque el max es per-policy (varía por + // type + isCustomRange). El catch del retry ya valida y marca failed si + // se excede. Los jobs con maxRetries=0 nunca llegan a status='pending', + // van directo a 'failed', así que no aparecen acá. + const pendingJobs = await prisma.satSyncJob.findMany({ + where: { + status: 'pending', + nextRetryAt: { lte: new Date() }, + }, + include: { tenant: { select: { id: true, databaseName: true, rfc: true } } }, + }); + + if (pendingJobs.length === 0) return; + + console.log(`[SAT Retry] ${pendingJobs.length} job(s) pendientes de reintento`); + + for (const job of pendingJobs) { + try { + // Verificar que no haya otro sync activo para el MISMO (tenant, contribuyente). + // Contribuyentes distintos pueden correr en paralelo. + const activeSync = await prisma.satSyncJob.findFirst({ + where: { + tenantId: job.tenantId, + contribuyenteId: job.contribuyenteId ?? null, + status: 'running', + }, + }); + + if (activeSync) { + console.log(`[SAT Retry] (${job.tenant.rfc}, contrib=${job.contribuyenteId || 'tenant-wide'}) tiene sync activo, posponiendo`); + await updateJobProgress(job.id, { + nextRetryAt: new Date(Date.now() + 60 * 60 * 1000), // +1 hora + }); + continue; + } + + console.log(`[SAT Retry] Reintentando job ${job.id} (${job.tenant.rfc}), intento ${job.retryCount}/${getRetryPolicy(job).maxRetries}`); + + // Try per-contribuyente FIEL first, then legacy + let decryptedFiel = null; + if (job.contribuyenteId) { + const pool = await tenantDb.getPool(job.tenantId, job.tenant.databaseName); + decryptedFiel = await getDecryptedFielContribuyente(pool, job.contribuyenteId); + } + if (!decryptedFiel) { + decryptedFiel = await getDecryptedFiel(job.tenantId); + } + if (!decryptedFiel) { + await updateJobProgress(job.id, { + status: 'failed', + errorMessage: 'FIEL no disponible para reintento', + completedAt: new Date(), + }); + continue; + } + + const service = createSatService({ + cerContent: decryptedFiel.cerContent, + keyContent: decryptedFiel.keyContent, + password: decryptedFiel.password, + }); + + const ctx: SyncContext = { + fielData: { + cerContent: decryptedFiel.cerContent, + keyContent: decryptedFiel.keyContent, + password: decryptedFiel.password, + }, + service, + rfc: decryptedFiel.rfc, + tenantId: job.tenantId, + databaseName: job.tenant.databaseName, + contribuyenteId: job.contribuyenteId ?? null, + getPool: () => tenantDb.getPool(job.tenantId, job.tenant.databaseName), + }; + + await updateJobProgress(job.id, { status: 'running', errorMessage: null as any }); + + // Re-ejecutar según tipo original + try { + if (job.type === 'initial') { + await processInitialSync(ctx, job.id, job.dateFrom, job.dateTo); + } else if (job.type === 'incremental') { + await processIncrementalSync(ctx, job.id); + } else { + await processDailySync(ctx, job.id); + } + + await updateJobProgress(job.id, { + status: 'completed', + completedAt: new Date(), + progressPercent: 100, + errorMessage: null as any, + }); + console.log(`[SAT Retry] Job ${job.id} completado en reintento ${job.retryCount}`); + } catch (retryError: any) { + console.error(`[SAT Retry] Job ${job.id} falló de nuevo:`, retryError.message); + + const isTimeout = retryError.message?.includes('Timeout'); + const policy = getRetryPolicy(job); + const nextRetryNumber = job.retryCount + 1; + const nextRetry = isTimeout && nextRetryNumber <= policy.maxRetries + ? computeNextRetryAt(job.startedAt!, nextRetryNumber, policy) + : null; + + if (nextRetry) { + await updateJobProgress(job.id, { + status: 'pending', + errorMessage: `Timeout (intento ${nextRetryNumber}/${policy.maxRetries}). Reintento programado para ${nextRetry.toLocaleString('es-MX')}.`, + retryCount: nextRetryNumber, + nextRetryAt: nextRetry, + }); + } else { + await updateJobProgress(job.id, { + status: 'failed', + errorMessage: isTimeout + ? 'Fallo conexión SAT, vuelve a intentar con un rango de fechas menor.' + : retryError.message, + completedAt: new Date(), + }); + } + } + } catch (error: any) { + console.error(`[SAT Retry] Error procesando job ${job.id}:`, error.message); + await updateJobProgress(job.id, { + status: 'failed', + errorMessage: error.message, + completedAt: new Date(), + }); + } + } +} + +/** + * Obtiene el estado actual de sincronización de un tenant + */ +export async function getSyncStatus(tenantId: string, contribuyenteId?: string): Promise<{ + hasActiveSync: boolean; + currentJob?: SatSyncJob; + lastCompletedJob?: SatSyncJob; + totalCfdisSynced: number; +}> { + const contribuyenteFilter = contribuyenteId ? { contribuyenteId } : {}; + + const activeJob = await prisma.satSyncJob.findFirst({ + where: { + tenantId, + ...contribuyenteFilter, + status: { in: ['pending', 'running'] }, + }, + orderBy: { createdAt: 'desc' }, + }); + + const lastCompleted = await prisma.satSyncJob.findFirst({ + where: { + tenantId, + ...contribuyenteFilter, + status: 'completed', + }, + orderBy: { completedAt: 'desc' }, + }); + + const totals = await prisma.satSyncJob.aggregate({ + where: { + tenantId, + ...contribuyenteFilter, + status: 'completed', + }, + _sum: { + cfdisInserted: true, + }, + }); + + const mapJob = (job: any): SatSyncJob => ({ + id: job.id, + tenantId: job.tenantId, + type: job.type, + status: job.status, + dateFrom: job.dateFrom.toISOString(), + dateTo: job.dateTo.toISOString(), + cfdiType: job.cfdiType ?? undefined, + satRequestId: job.satRequestId ?? undefined, + satPackageIds: job.satPackageIds, + cfdisFound: job.cfdisFound, + cfdisDownloaded: job.cfdisDownloaded, + cfdisInserted: job.cfdisInserted, + cfdisUpdated: job.cfdisUpdated, + progressPercent: job.progressPercent, + errorMessage: job.errorMessage ?? undefined, + startedAt: job.startedAt?.toISOString(), + completedAt: job.completedAt?.toISOString(), + createdAt: job.createdAt.toISOString(), + retryCount: job.retryCount, + }); + + return { + hasActiveSync: !!activeJob, + currentJob: activeJob ? mapJob(activeJob) : undefined, + lastCompletedJob: lastCompleted ? mapJob(lastCompleted) : undefined, + totalCfdisSynced: totals._sum.cfdisInserted || 0, + }; +} + +/** + * Obtiene el historial de sincronizaciones + */ +export async function getSyncHistory( + tenantId: string, + page: number = 1, + limit: number = 10, + contribuyenteId?: string +): Promise<{ jobs: SatSyncJob[]; total: number }> { + const contribuyenteFilter = contribuyenteId ? { contribuyenteId } : {}; + + const [jobs, total] = await Promise.all([ + prisma.satSyncJob.findMany({ + where: { tenantId, ...contribuyenteFilter }, + orderBy: { createdAt: 'desc' }, + skip: (page - 1) * limit, + take: limit, + }), + prisma.satSyncJob.count({ where: { tenantId, ...contribuyenteFilter } }), + ]); + + return { + jobs: jobs.map(job => ({ + id: job.id, + tenantId: job.tenantId, + type: job.type, + status: job.status, + dateFrom: job.dateFrom.toISOString(), + dateTo: job.dateTo.toISOString(), + cfdiType: job.cfdiType ?? undefined, + satRequestId: job.satRequestId ?? undefined, + satPackageIds: job.satPackageIds, + cfdisFound: job.cfdisFound, + cfdisDownloaded: job.cfdisDownloaded, + cfdisInserted: job.cfdisInserted, + cfdisUpdated: job.cfdisUpdated, + progressPercent: job.progressPercent, + errorMessage: job.errorMessage ?? undefined, + startedAt: job.startedAt?.toISOString(), + completedAt: job.completedAt?.toISOString(), + createdAt: job.createdAt.toISOString(), + retryCount: job.retryCount, + })), + total, + }; +} + +/** + * Reintenta un job fallido + */ +export async function retryJob(jobId: string): Promise { + const job = await prisma.satSyncJob.findUnique({ + where: { id: jobId }, + }); + + if (!job) { + throw new Error('Job no encontrado'); + } + + if (job.status !== 'failed') { + throw new Error('Solo se pueden reintentar jobs fallidos'); + } + + return startSync(job.tenantId, job.type, job.dateFrom, job.dateTo); +} diff --git a/apps/api/src/services/sat/sweep-stale-jobs.service.ts b/apps/api/src/services/sat/sweep-stale-jobs.service.ts new file mode 100644 index 0000000..d0af285 --- /dev/null +++ b/apps/api/src/services/sat/sweep-stale-jobs.service.ts @@ -0,0 +1,98 @@ +import { prisma } from '../../config/database.js'; + +export interface SweepResult { + pendingFound: number; + runningFound: number; + pendingMarked: number; + runningMarked: number; + entries: Array<{ + id: string; + tenantId: string; + kind: 'pending-stale' | 'running-stale'; + ageHours: number; + }>; +} + +/** + * Watchdog para jobs `sat_sync_jobs` stale. + * + * Categorías: + * 1. `pending` con `nextRetryAt` > pendingHours atrás. El cron horario + * `retryTimedOutJobs` normalmente los retoma, pero si no arranca + * (dev, caída, reinicio largo) el job queda colgado y bloquea el + * lock para nuevos syncs del mismo (tenant, contribuyente). + * + * 2. `running` con `startedAt` > runningHours atrás. Un sync inicial + * típico termina en <2h; si lleva >runningHours es casi seguro + * huérfano de un proceso que murió. La solicitud SAT ya expiró. + * + * Marca ambos como `failed` con `errorMessage` descriptivo. Idempotente + * (volver a correrlo no reabre los ya-marcados-failed). + * + * - `apply=false` (default): dry-run, no toca BD. + * - `pendingHours`/`runningHours`: thresholds (default 12h / 4h). + */ +export async function sweepStaleSatJobs(params: { + apply: boolean; + pendingHours?: number; + runningHours?: number; +} = { apply: false }): Promise { + const pendingHours = params.pendingHours ?? 12; + const runningHours = params.runningHours ?? 4; + const now = new Date(); + const pendingCutoff = new Date(now.getTime() - pendingHours * 3600 * 1000); + const runningCutoff = new Date(now.getTime() - runningHours * 3600 * 1000); + + const stalePending = await prisma.satSyncJob.findMany({ + where: { status: 'pending', nextRetryAt: { lt: pendingCutoff } }, + orderBy: { createdAt: 'asc' }, + }); + const staleRunning = await prisma.satSyncJob.findMany({ + where: { status: 'running', startedAt: { lt: runningCutoff } }, + orderBy: { createdAt: 'asc' }, + }); + + const result: SweepResult = { + pendingFound: stalePending.length, + runningFound: staleRunning.length, + pendingMarked: 0, + runningMarked: 0, + entries: [], + }; + + for (const j of stalePending) { + const ageHours = Math.round((now.getTime() - (j.nextRetryAt ?? j.createdAt).getTime()) / 3_600_000); + result.entries.push({ id: j.id, tenantId: j.tenantId, kind: 'pending-stale', ageHours }); + } + for (const j of staleRunning) { + const ageHours = Math.round((now.getTime() - (j.startedAt ?? j.createdAt).getTime()) / 3_600_000); + result.entries.push({ id: j.id, tenantId: j.tenantId, kind: 'running-stale', ageHours }); + } + + if (!params.apply) return result; + + for (const j of stalePending) { + await prisma.satSyncJob.update({ + where: { id: j.id }, + data: { + status: 'failed', + completedAt: now, + errorMessage: `Abandoned by watchdog: pending with nextRetryAt ${j.nextRetryAt?.toISOString()} > ${pendingHours}h in the past. Retry cron didn't pick it up.`, + }, + }); + result.pendingMarked++; + } + for (const j of staleRunning) { + await prisma.satSyncJob.update({ + where: { id: j.id }, + data: { + status: 'failed', + completedAt: now, + errorMessage: `Abandoned by watchdog: running with startedAt ${j.startedAt?.toISOString()} > ${runningHours}h (process crash / orphan). SAT request is lost; re-launch manually.`, + }, + }); + result.runningMarked++; + } + + return result; +} diff --git a/apps/api/src/services/tareas.service.ts b/apps/api/src/services/tareas.service.ts new file mode 100644 index 0000000..1be1b80 --- /dev/null +++ b/apps/api/src/services/tareas.service.ts @@ -0,0 +1,467 @@ +import type { Pool } from 'pg'; + +export type Recurrencia = + | 'semanal' | 'quincenal' | 'mensual' + | 'bimestral' | 'trimestral' | 'semestral' | 'anual'; + +export interface TareaCatalogo { + id: string; + contribuyenteId: string; + nombre: string; + descripcion: string | null; + recurrencia: Recurrencia; + diaSemana: number | null; + diaMes: number | null; + soloSupervisorCompleta: boolean; + esDefault: boolean; + active: boolean; + orden: number; + createdAt: Date; +} + +export interface TareaPeriodo { + id: string; + tareaId: string; + periodo: string; + fechaLimite: Date; + completada: boolean; + completadaAt: Date | null; + completadaPor: string | null; + notas: string | null; +} + +export interface TareaConPeriodo extends TareaCatalogo { + periodoActual: TareaPeriodo | null; +} + +const ROW_TO_TAREA = (r: any): TareaCatalogo => ({ + id: r.id, + contribuyenteId: r.contribuyente_id, + nombre: r.nombre, + descripcion: r.descripcion, + recurrencia: r.recurrencia, + diaSemana: r.dia_semana, + diaMes: r.dia_mes, + soloSupervisorCompleta: r.solo_supervisor_completa, + esDefault: r.es_default, + active: r.active, + orden: r.orden, + createdAt: r.created_at, +}); + +const ROW_TO_PERIODO = (r: any): TareaPeriodo => ({ + id: r.id, + tareaId: r.tarea_id, + periodo: r.periodo, + fechaLimite: r.fecha_limite, + completada: r.completada, + completadaAt: r.completada_at, + completadaPor: r.completada_por, + notas: r.notas, +}); + +function sanitizeUuid(id: string): string { + return id.replace(/[^a-f0-9-]/gi, ''); +} + +// ─── Catálogo CRUD ─── + +export async function listTareas(pool: Pool, contribuyenteId: string): Promise { + const { rows } = await pool.query( + `SELECT * FROM tareas_catalogo + WHERE contribuyente_id = $1 AND active = true + ORDER BY orden, nombre`, + [sanitizeUuid(contribuyenteId)], + ); + return rows.map(ROW_TO_TAREA); +} + +export interface TareaInput { + nombre: string; + descripcion?: string | null; + recurrencia: Recurrencia; + diaSemana?: number | null; + diaMes?: number | null; + soloSupervisorCompleta?: boolean; + orden?: number; +} + +export async function createTarea( + pool: Pool, + contribuyenteId: string, + data: TareaInput, +): Promise { + const { rows: [r] } = await pool.query( + `INSERT INTO tareas_catalogo + (contribuyente_id, nombre, descripcion, recurrencia, + dia_semana, dia_mes, solo_supervisor_completa, orden) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *`, + [ + sanitizeUuid(contribuyenteId), + data.nombre, + data.descripcion ?? null, + data.recurrencia, + data.diaSemana ?? null, + data.diaMes ?? null, + data.soloSupervisorCompleta ?? false, + data.orden ?? 0, + ], + ); + return ROW_TO_TAREA(r); +} + +export async function updateTarea( + pool: Pool, + tareaId: string, + data: Partial, +): Promise { + const fields: string[] = []; + const values: unknown[] = []; + let i = 1; + if (data.nombre !== undefined) { fields.push(`nombre = $${i++}`); values.push(data.nombre); } + if (data.descripcion !== undefined) { fields.push(`descripcion = $${i++}`); values.push(data.descripcion); } + if (data.recurrencia !== undefined) { fields.push(`recurrencia = $${i++}`); values.push(data.recurrencia); } + if (data.diaSemana !== undefined) { fields.push(`dia_semana = $${i++}`); values.push(data.diaSemana); } + if (data.diaMes !== undefined) { fields.push(`dia_mes = $${i++}`); values.push(data.diaMes); } + if (data.soloSupervisorCompleta !== undefined) { fields.push(`solo_supervisor_completa = $${i++}`); values.push(data.soloSupervisorCompleta); } + if (data.orden !== undefined) { fields.push(`orden = $${i++}`); values.push(data.orden); } + if (fields.length === 0) return null; + values.push(sanitizeUuid(tareaId)); + const { rows: [r] } = await pool.query( + `UPDATE tareas_catalogo SET ${fields.join(', ')} WHERE id = $${i} RETURNING *`, + values, + ); + return r ? ROW_TO_TAREA(r) : null; +} + +export async function deleteTarea(pool: Pool, tareaId: string): Promise { + const { rowCount } = await pool.query( + `UPDATE tareas_catalogo SET active = false WHERE id = $1`, + [sanitizeUuid(tareaId)], + ); + return (rowCount ?? 0) > 0; +} + +// ─── Materialización de periodos ─── + +/** Convierte una fecha a su `periodo` según recurrencia. */ +function periodoForDate(date: Date, recurrencia: Recurrencia): string { + const año = date.getFullYear(); + const mes = date.getMonth() + 1; + if (recurrencia === 'semanal' || recurrencia === 'quincenal') { + return `${año}-W${String(isoWeek(date)).padStart(2, '0')}`; + } + if (recurrencia === 'mensual') return `${año}-${String(mes).padStart(2, '0')}`; + if (recurrencia === 'bimestral') return `${año}-B${Math.ceil(mes / 2)}`; + if (recurrencia === 'trimestral') return `${año}-Q${Math.ceil(mes / 3)}`; + if (recurrencia === 'semestral') return `${año}-S${Math.ceil(mes / 6)}`; + return `${año}`; +} + +function isoWeek(date: Date): number { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); +} + +/** + * Calcula la fecha límite del próximo periodo de una tarea, basado en + * `recurrencia`, `dia_semana`/`dia_mes` y un punto de referencia. + * + * Para semanal/quincenal: primera ocurrencia del dia_semana en/después de + * `fromDate`. (Quincenal alterna con el mismo dia_semana cada 14 días.) + * Para mensual+: dia_mes del periodo en curso de `fromDate`. Si el dia_mes + * excede el último día del mes, se clampa al último día. + */ +function computeFechaLimite( + recurrencia: Recurrencia, + diaSemana: number | null, + diaMes: number | null, + fromDate: Date, +): Date { + if (recurrencia === 'semanal' || recurrencia === 'quincenal') { + const target = (diaSemana ?? 5); + const d = new Date(fromDate); + const current = d.getDay() === 0 ? 7 : d.getDay(); + const diff = (target - current + 7) % 7; + d.setDate(d.getDate() + diff); + return d; + } + // mensual a anual: usar el mes "ancla" del periodo + const año = fromDate.getFullYear(); + let mesAncla = fromDate.getMonth() + 1; + if (recurrencia === 'bimestral') mesAncla = Math.ceil(mesAncla / 2) * 2; + else if (recurrencia === 'trimestral') mesAncla = Math.ceil(mesAncla / 3) * 3; + else if (recurrencia === 'semestral') mesAncla = Math.ceil(mesAncla / 6) * 6; + else if (recurrencia === 'anual') mesAncla = 12; + const lastDay = new Date(año, mesAncla, 0).getDate(); + const dia = Math.min(diaMes ?? lastDay, lastDay); + return new Date(año, mesAncla - 1, dia); +} + +/** + * Asegura que existan los periodos vigentes (presente + futuros próximos) + * para todas las tareas activas del contribuyente. Solo crea periodos + * cuya `fecha_limite >= today` (no retroactivos, igual que obligaciones). + * + * Para cada tarea genera el periodo CURRENT (donde cae hoy) si su fecha + * límite aún no ha pasado, o el NEXT si ya pasó. + */ +export async function materializarPeriodos( + pool: Pool, + contribuyenteId: string, +): Promise { + const tareas = await listTareas(pool, contribuyenteId); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + for (const t of tareas) { + let fechaLimite = computeFechaLimite(t.recurrencia, t.diaSemana, t.diaMes, today); + // Si la fecha límite calculada para "hoy" ya pasó, salta al siguiente periodo + while (fechaLimite < today) { + const next = new Date(fechaLimite); + const recurrenciaIncrement: Record void> = { + semanal: () => next.setDate(next.getDate() + 7), + quincenal: () => next.setDate(next.getDate() + 14), + mensual: () => next.setMonth(next.getMonth() + 1), + bimestral: () => next.setMonth(next.getMonth() + 2), + trimestral: () => next.setMonth(next.getMonth() + 3), + semestral: () => next.setMonth(next.getMonth() + 6), + anual: () => next.setFullYear(next.getFullYear() + 1), + }; + recurrenciaIncrement[t.recurrencia](); + fechaLimite = computeFechaLimite(t.recurrencia, t.diaSemana, t.diaMes, next); + } + + const periodo = periodoForDate(fechaLimite, t.recurrencia); + await pool.query( + `INSERT INTO tarea_periodos (tarea_id, periodo, fecha_limite) + VALUES ($1, $2, $3) + ON CONFLICT (tarea_id, periodo) DO NOTHING`, + [t.id, periodo, fechaLimite.toISOString().split('T')[0]], + ); + } +} + +/** + * Lee tareas activas del contribuyente con su periodo más cercano (vigente + * o pendiente). Materializa los faltantes antes de leer. + */ +export async function listTareasConPeriodoActual( + pool: Pool, + contribuyenteId: string, +): Promise { + await materializarPeriodos(pool, contribuyenteId); + const tareas = await listTareas(pool, contribuyenteId); + if (tareas.length === 0) return []; + + const ids = tareas.map(t => t.id); + const today = new Date().toISOString().split('T')[0]; + const { rows } = await pool.query( + `SELECT DISTINCT ON (tarea_id) * + FROM tarea_periodos + WHERE tarea_id = ANY($1::uuid[]) + AND (completada = false OR fecha_limite >= $2::date) + ORDER BY tarea_id, fecha_limite ASC`, + [ids, today], + ); + const periodos = new Map(rows.map(r => [r.tarea_id, ROW_TO_PERIODO(r)])); + return tareas.map(t => ({ ...t, periodoActual: periodos.get(t.id) ?? null })); +} + +// ─── Completar / descompletar periodo ─── + +const ROLES_SUPERVISOR = new Set(['owner', 'cfo', 'supervisor']); + +/** + * Marca un periodo como completado. Si la tarea tiene + * `solo_supervisor_completa=true`, valida que el rol del usuario sea + * owner/cfo/supervisor. Devuelve el periodo actualizado y la tarea + * (para que el caller pueda disparar notificaciones). + */ +export async function completarPeriodo( + pool: Pool, + periodoId: string, + userId: string, + userRole: string, + notas: string | null = null, +): Promise<{ periodo: TareaPeriodo; tarea: TareaCatalogo } | null> { + const { rows: [pRow] } = await pool.query( + `SELECT tp.*, tc.solo_supervisor_completa + FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tp.id = $1`, + [sanitizeUuid(periodoId)], + ); + if (!pRow) return null; + if (pRow.solo_supervisor_completa && !ROLES_SUPERVISOR.has(userRole)) { + throw new Error('Solo supervisor o owner pueden marcar esta tarea como completada'); + } + const { rows: [updated] } = await pool.query( + `UPDATE tarea_periodos + SET completada = true, completada_at = NOW(), completada_por = $2, notas = $3 + WHERE id = $1 + RETURNING *`, + [sanitizeUuid(periodoId), userId, notas], + ); + const { rows: [tareaRow] } = await pool.query( + `SELECT * FROM tareas_catalogo WHERE id = $1`, + [pRow.tarea_id], + ); + return { periodo: ROW_TO_PERIODO(updated), tarea: ROW_TO_TAREA(tareaRow) }; +} + +export async function descompletarPeriodo( + pool: Pool, + periodoId: string, +): Promise { + const { rowCount } = await pool.query( + `UPDATE tarea_periodos + SET completada = false, completada_at = NULL, completada_por = NULL + WHERE id = $1`, + [sanitizeUuid(periodoId)], + ); + return (rowCount ?? 0) > 0; +} + +// ─── Calendario / alertas ─── + +export interface TareaEventoCalendario { + titulo: string; + descripcion: string; + tipo: 'tarea'; + fechaLimite: string; + recurrencia: string; + completado: boolean; + notas: string | null; + contribuyenteId: string; + tareaId: string; + periodoId: string; +} + +/** + * Lee tareas + sus periodos del año para mostrar en /calendario. Materializa + * los faltantes antes de leer (mismo patrón que listTareasConPeriodoActual). + * Si no hay contribuyenteId, no retorna nada (las tareas son siempre por + * contribuyente). + */ +export async function getEventosTareasParaCalendario( + pool: Pool, + contribuyenteId: string, + año: number, +): Promise { + await materializarPeriodos(pool, contribuyenteId); + const { rows } = await pool.query( + `SELECT tp.id AS periodo_id, tp.tarea_id, tp.fecha_limite, tp.completada, tp.notas, + tc.nombre, tc.descripcion, tc.recurrencia, tc.contribuyente_id + FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.contribuyente_id = $1 + AND tc.active = true + AND EXTRACT(YEAR FROM tp.fecha_limite) = $2`, + [sanitizeUuid(contribuyenteId), año], + ); + return rows.map(r => ({ + titulo: r.nombre, + descripcion: r.descripcion ?? '', + tipo: 'tarea' as const, + fechaLimite: r.fecha_limite instanceof Date ? r.fecha_limite.toISOString().split('T')[0] : String(r.fecha_limite), + recurrencia: r.recurrencia, + completado: r.completada, + notas: r.notas, + contribuyenteId: r.contribuyente_id, + tareaId: r.tarea_id, + periodoId: r.periodo_id, + })); +} + +/** + * Cuenta tareas próximas a vencer (≤3 días) para alertas auto. Solo cuenta + * los periodos pendientes del contribuyente filtrado o de todo el tenant. + */ +export async function contarTareasProximasVencer( + pool: Pool, + contribuyenteId: string | null | undefined, +): Promise<{ total: number; monto?: number }> { + const safeId = contribuyenteId ? sanitizeUuid(contribuyenteId) : null; + if (safeId) await materializarPeriodos(pool, safeId); + const cf = safeId ? `AND tc.contribuyente_id = '${safeId}'` : ''; + const { rows: [r] } = await pool.query( + `SELECT COUNT(*)::int AS total + FROM tarea_periodos tp + JOIN tareas_catalogo tc ON tc.id = tp.tarea_id + WHERE tc.active = true + AND tp.completada = false + AND tp.fecha_limite >= CURRENT_DATE + AND tp.fecha_limite <= CURRENT_DATE + INTERVAL '3 days' + ${cf}`, + ); + return { total: r?.total || 0 }; +} + +/** + * Resuelve el auxiliar asignado a la cartera del contribuyente (si existe). + * Busca primero en subcarteras (más específico) y luego en la top-level. + * Retorna null si el contribuyente no está en ninguna cartera o ninguna + * tiene auxiliar asignado. + */ +export async function getAuxiliarUserIdDeContribuyente( + pool: Pool, + contribuyenteId: string, +): Promise { + const { rows } = await pool.query<{ auxiliar_user_id: string | null; parent_id: string | null }>( + `SELECT c.auxiliar_user_id, c.parent_id + FROM cartera_entidades ce + JOIN carteras c ON c.id = ce.cartera_id + WHERE ce.entidad_id = $1 + ORDER BY (c.parent_id IS NOT NULL) DESC, c.created_at DESC`, + [sanitizeUuid(contribuyenteId)], + ); + for (const row of rows) { + if (row.auxiliar_user_id) return row.auxiliar_user_id; + } + return null; +} + +// ─── Seed de tareas default ─── + +const TAREAS_DEFAULT: TareaInput[] = [ + { nombre: 'Solicitar estados de cuenta', descripcion: 'Pedir al cliente los estados de cuenta bancarios del mes', recurrencia: 'mensual', diaMes: 5, orden: 1 }, + { nombre: 'Realizar conciliación bancaria', descripcion: 'Conciliar los movimientos bancarios contra los CFDIs', recurrencia: 'mensual', diaMes: 10, orden: 2 }, + { nombre: 'Realizar contabilización', descripcion: 'Registrar los CFDIs y movimientos en la contabilidad', recurrencia: 'mensual', diaMes: 14, orden: 3 }, + { nombre: 'Revisión fiscal preliminar', descripcion: 'Revisar declaración antes de presentación (solo supervisor/owner)', recurrencia: 'mensual', diaMes: 15, soloSupervisorCompleta: true, orden: 4 }, +]; + +export async function seedTareasDefault( + pool: Pool, + contribuyenteId: string, +): Promise { + const existentes = await pool.query( + `SELECT 1 FROM tareas_catalogo WHERE contribuyente_id = $1 AND es_default = true LIMIT 1`, + [sanitizeUuid(contribuyenteId)], + ); + if (existentes.rowCount && existentes.rowCount > 0) return 0; + + let count = 0; + for (const t of TAREAS_DEFAULT) { + await pool.query( + `INSERT INTO tareas_catalogo + (contribuyente_id, nombre, descripcion, recurrencia, dia_mes, solo_supervisor_completa, es_default, orden) + VALUES ($1, $2, $3, $4, $5, $6, true, $7)`, + [ + sanitizeUuid(contribuyenteId), + t.nombre, + t.descripcion ?? null, + t.recurrencia, + t.diaMes ?? null, + t.soloSupervisorCompleta ?? false, + t.orden ?? 0, + ], + ); + count++; + } + return count; +} diff --git a/apps/api/src/services/tenants.service.ts b/apps/api/src/services/tenants.service.ts new file mode 100644 index 0000000..3180e11 --- /dev/null +++ b/apps/api/src/services/tenants.service.ts @@ -0,0 +1,399 @@ +import { prisma, tenantDb } from '../config/database.js'; +import { DESPACHO_PLANS, type DespachoPlan } from '@horux/shared'; +import { emailService } from './email/email.service.js'; +import * as metabaseService from './metabase.service.js'; +import { randomBytes } from 'crypto'; +import bcrypt from 'bcryptjs'; + +export async function getAllTenants() { + return prisma.tenant.findMany({ + where: { active: true }, + select: { + id: true, + nombre: true, + rfc: true, + plan: true, + databaseName: true, + createdAt: true, + _count: { + select: { memberships: { where: { active: true } } as any } + } + }, + orderBy: { nombre: 'asc' } + }); +} + +export async function getTenantById(id: string) { + return prisma.tenant.findUnique({ + where: { id }, + select: { + id: true, + nombre: true, + rfc: true, + plan: true, + databaseName: true, + createdAt: true, + } + }); +} + +export async function createTenant(data: { + nombre: string; + rfc: string; + plan?: DespachoPlan; + adminEmail: string; + adminNombre: string; + amount: number; + /** Solo plan custom: primera fecha de pago (deadline para que el cliente + * complete su primer cobro). Se persiste como Subscription.currentPeriodEnd. */ + firstPaymentDueAt?: string | null; +}) { + const plan = data.plan || 'trial'; + + // 1. Provision a dedicated database for this tenant + const databaseName = await tenantDb.provisionDatabase(data.rfc); + + // 1b. Register tenant database in Metabase (non-blocking, logs errors only) + metabaseService.registerDatabase({ + nombre: data.nombre, + dbName: databaseName, + }).catch(err => console.error('[METABASE] Register failed:', err)); + + // 2. Create tenant record + const tenant = await prisma.tenant.create({ + data: { + nombre: data.nombre, + rfc: data.rfc.toUpperCase(), + plan, + databaseName, + } + }); + + // 3. Create admin user with temp password + const tempPassword = randomBytes(4).toString('hex'); // 8-char random + const hashedPassword = await bcrypt.hash(tempPassword, 10); + + // Get owner role ID from roles table (rol que asignamos al dueño del tenant al crearlo) + const ownerRol = await prisma.rol.findUnique({ where: { nombre: 'owner' } }); + if (!ownerRol) throw new Error('Rol owner no encontrado en la base de datos'); + + const user = await prisma.user.create({ + data: { + email: data.adminEmail, + passwordHash: hashedPassword, + nombre: data.adminNombre, + lastTenantId: tenant.id, + }, + }); + + // Crea membership owner del nuevo user en su tenant (fase 4 multi-tenant) + await prisma.tenantMembership.create({ + data: { + userId: user.id, + tenantId: tenant.id, + rolId: ownerRol.id, + isOwner: true, + active: true, + }, + }); + + // 4. Create initial subscription. Para plan custom, si admin proveyó + // firstPaymentDueAt, lo guardamos como currentPeriodEnd — sirve como + // deadline visible al cliente para realizar su primer pago. + const firstPayment = plan === 'custom' && data.firstPaymentDueAt + ? new Date(data.firstPaymentDueAt) + : null; + await prisma.subscription.create({ + data: { + tenantId: tenant.id, + plan, + status: 'pending', + amount: data.amount, + frequency: 'monthly', + ...(firstPayment ? { currentPeriodStart: new Date(), currentPeriodEnd: firstPayment } : {}), + }, + }); + + // 5. Send welcome email to client (non-blocking) + emailService.sendWelcome(data.adminEmail, { + nombre: data.adminNombre, + email: data.adminEmail, + tempPassword, + }).catch(err => console.error('[EMAIL] Welcome email failed:', err)); + + // 6. Send new client notification to admin with DB credentials + emailService.sendNewClientAdmin({ + clienteNombre: data.nombre, + clienteRfc: data.rfc.toUpperCase(), + adminEmail: data.adminEmail, + adminNombre: data.adminNombre, + tempPassword, + databaseName, + plan, + }).catch(err => console.error('[EMAIL] New client admin email failed:', err)); + + return { tenant, user, tempPassword }; +} + +/** + * Flow "Agregar empresa" — un user existente (típicamente owner) agrega un + * segundo RFC bajo su cuenta. A diferencia de `createTenant()`, NO crea un user + * nuevo: el caller se vuelve owner de la empresa nueva vía TenantMembership. + * + * Sin trial por default — el check de trial por owner (fase 5) bloquearía + * cualquier intento de re-activarlo. El owner contrata el plan desde la página + * de suscripción del nuevo tenant tras esta llamada. + */ +export async function addTenantToOwner(data: { + userId: string; + nombre: string; + rfc: string; + plan?: DespachoPlan; +}) { + const plan = data.plan || 'trial'; + const rfcUpper = data.rfc.toUpperCase(); + + // Valida que el RFC no exista en el sistema + const existingTenant = await prisma.tenant.findUnique({ where: { rfc: rfcUpper } }); + if (existingTenant) { + throw new Error('Ya existe una empresa con ese RFC en el sistema'); + } + + // Valida que el user exista y esté activo + const user = await prisma.user.findUnique({ where: { id: data.userId } }); + if (!user || !user.active) throw new Error('Usuario no encontrado'); + + // 1. Provision BD dedicada + const databaseName = await tenantDb.provisionDatabase(rfcUpper); + + // 1b. Register tenant database in Metabase (non-blocking, logs errors only) + metabaseService.registerDatabase({ + nombre: data.nombre, + dbName: databaseName, + }).catch(err => console.error('[METABASE] Register failed:', err)); + + // 2. Crea el tenant + const tenant = await prisma.tenant.create({ + data: { + nombre: data.nombre, + rfc: rfcUpper, + plan, + databaseName, + }, + }); + + // 3. Crea membership del caller como owner + const ownerRol = await prisma.rol.findUnique({ where: { nombre: 'owner' } }); + if (!ownerRol) throw new Error('Rol owner no encontrado'); + + await prisma.tenantMembership.create({ + data: { + userId: user.id, + tenantId: tenant.id, + rolId: ownerRol.id, + isOwner: true, + active: true, + }, + }); + + // 4. Subscripción pending (se activa al contratar un plan) + await prisma.subscription.create({ + data: { + tenantId: tenant.id, + plan, + status: 'pending', + amount: 0, // el precio real se setea al contratar + frequency: 'monthly', + }, + }); + + return { tenant }; +} + +/** + * Lista detallada de tenants del user actual con estado de suscripción. Usado + * por la página `/mis-empresas` — incluye plan, status, currentPeriodEnd, + * pendingPlan si aplica. + * + * @param onlyOwner filtra a solo memberships donde isOwner=true. Default true: + * un user contador que trabaja en empresa ajena NO la ve aquí, pero sí sus + * propias empresas donde es owner. El header switcher usa un endpoint distinto. + */ +export async function getMyTenantsDetailed(userId: string, onlyOwner = true) { + const memberships = await prisma.tenantMembership.findMany({ + where: { userId, active: true, ...(onlyOwner ? { isOwner: true } : {}) }, + include: { + tenant: { + include: { + subscriptions: { + orderBy: { createdAt: 'desc' }, + take: 1, + }, + }, + }, + rol: { select: { nombre: true } }, + }, + orderBy: { joinedAt: 'asc' }, + }); + + return memberships + .filter(m => m.tenant.active) + .map(m => { + const sub = m.tenant.subscriptions[0] || null; + return { + tenantId: m.tenant.id, + nombre: m.tenant.nombre, + rfc: m.tenant.rfc, + plan: m.tenant.plan, + role: m.rol.nombre, + isOwner: m.isOwner, + trialEndsAt: m.tenant.trialEndsAt, + subscription: sub ? { + status: sub.status, + plan: sub.plan, + amount: sub.amount ? Number(sub.amount) : 0, + frequency: sub.frequency, + currentPeriodEnd: sub.currentPeriodEnd, + pendingPlan: sub.pendingPlan, + pendingEffectiveAt: sub.pendingEffectiveAt, + } : null, + }; + }); +} + +export async function updateTenant(id: string, data: { + nombre?: string; + rfc?: string; + plan?: DespachoPlan; + active?: boolean; +}) { + return prisma.tenant.update({ + where: { id }, + data: { + ...(data.nombre && { nombre: data.nombre }), + ...(data.rfc && { rfc: data.rfc.toUpperCase() }), + ...(data.plan && { plan: data.plan }), + ...(data.active !== undefined && { active: data.active }), + }, + select: { + id: true, + nombre: true, + rfc: true, + plan: true, + databaseName: true, + active: true, + createdAt: true, + } + }); +} + +export async function getDatosFiscales(id: string) { + return prisma.tenant.findUnique({ + where: { id }, + select: { + codigoPostal: true, + calle: true, + numExterior: true, + numInterior: true, + colonia: true, + ciudad: true, + municipio: true, + estado: true, + telefono: true, + }, + }); +} + +export async function updateDatosFiscales(id: string, data: { + codigoPostal?: string; + calle?: string; + numExterior?: string; + numInterior?: string; + colonia?: string; + ciudad?: string; + municipio?: string; + estado?: string; + telefono?: string; +}) { + return prisma.tenant.update({ + where: { id }, + data, + select: { + codigoPostal: true, + calle: true, + numExterior: true, + numInterior: true, + colonia: true, + ciudad: true, + municipio: true, + estado: true, + telefono: true, + }, + }); +} + +/** + * Preferencias de auto-facturación de pagos de suscripción. + * Lee también los regímenes activos del tenant — útil para que la UI muestre + * las opciones del dropdown "Régimen preferido" sin queries adicionales. + */ +export async function getPreferenciasFacturacion(id: string) { + const tenant = await prisma.tenant.findUnique({ + where: { id }, + select: { + factPreferencia: true, + factUsoCfdi: true, + factRegimenPreferido: true, + regimenesActivos: { + select: { regimen: { select: { clave: true, descripcion: true } } }, + orderBy: { createdAt: 'asc' }, + }, + }, + }); + if (!tenant) return null; + return { + factPreferencia: tenant.factPreferencia, + factUsoCfdi: tenant.factUsoCfdi, + factRegimenPreferido: tenant.factRegimenPreferido, + regimenesActivos: tenant.regimenesActivos.map(ra => ra.regimen), + }; +} + +export async function updatePreferenciasFacturacion(id: string, data: { + factPreferencia?: 'publico_general' | 'mis_datos'; + factUsoCfdi?: string; + factRegimenPreferido?: string | null; +}) { + return prisma.tenant.update({ + where: { id }, + data, + select: { + factPreferencia: true, + factUsoCfdi: true, + factRegimenPreferido: true, + }, + }); +} + +export async function deleteTenant(id: string) { + const tenant = await prisma.tenant.findUnique({ + where: { id }, + select: { databaseName: true }, + }); + + // Soft-delete the tenant record + await prisma.tenant.update({ + where: { id }, + data: { active: false } + }); + + // Soft-delete the database (rename with _deleted_ suffix) + if (tenant) { + await tenantDb.deprovisionDatabase(tenant.databaseName); + tenantDb.invalidatePool(id); + // Remove from Metabase (non-blocking) + metabaseService.deleteDatabase(tenant.databaseName).catch(err => + console.error('[METABASE] Delete failed:', err) + ); + } +} diff --git a/apps/api/src/services/usuarios.service.ts b/apps/api/src/services/usuarios.service.ts new file mode 100644 index 0000000..03a96e4 --- /dev/null +++ b/apps/api/src/services/usuarios.service.ts @@ -0,0 +1,251 @@ +import { prisma } from '../config/database.js'; +import bcrypt from 'bcryptjs'; +import { randomBytes } from 'crypto'; +import { getDespachoPlanLimits } from './plan-catalogo.service.js'; +import type { UserListItem, UserInvite, UserUpdate, Role } from '@horux/shared'; + +/** + * Refactor F6.2 (multi-tenant): los users se listan/invitan/borran vía + * `tenant_memberships`. `User.tenantId` y `User.rolId` se siguen poblando al + * crear (legacy, F6.4 los borra) pero NO se leen en este servicio. + * + * - getUsuarios(tenantId) → memberships activos en ese tenant + * - inviteUsuario → crea User + Membership (siempre isOwner=false aquí) + * - updateUsuario → cambia role en la membership de ese tenant; active es global + * - deleteUsuario → desactiva membership (soft delete por tenant). El user + * sigue existiendo si tiene otras memberships. + */ + +async function getRolId(nombre: string): Promise { + const rol = await prisma.rol.findUnique({ where: { nombre } }); + if (!rol) throw new Error(`Rol '${nombre}' no encontrado`); + return rol.id; +} + +/** Mapea una row de membership con user joineado al shape UserListItem. */ +function mapMembershipRow(m: any, includeTenant = false): UserListItem { + return { + id: m.user.id, + email: m.user.email, + nombre: m.user.nombre, + role: m.rol.nombre as Role, + active: m.user.active && m.active, // user activo Y membership activa + lastLogin: m.user.lastLogin?.toISOString() || null, + createdAt: m.user.createdAt.toISOString(), + ...(includeTenant && { tenantId: m.tenantId, tenantName: m.tenant.nombre }), + }; +} + +const MEMBERSHIP_INCLUDE = { + user: { + select: { + id: true, + email: true, + nombre: true, + active: true, + lastLogin: true, + createdAt: true, + }, + }, + rol: { select: { nombre: true } }, +}; + +export async function getUsuarios(tenantId: string): Promise { + const memberships = await prisma.tenantMembership.findMany({ + where: { tenantId, active: true }, + include: MEMBERSHIP_INCLUDE, + orderBy: { joinedAt: 'desc' }, + }); + + return memberships.map(m => mapMembershipRow(m)); +} + +export async function inviteUsuario(tenantId: string, data: UserInvite): Promise { + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { plan: true }, + }); + + // Límite del catálogo despacho desde BD (con cache). -1 = ilimitado. + // Si el plan no existe en BD (shouldn't happen post-seed) cae a 1 como + // mínimo defensivo para no permitir invitaciones masivas accidentales. + const planLimits = tenant ? await getDespachoPlanLimits(tenant.plan) : null; + const maxUsers = planLimits?.maxUsers ?? 1; + + // Cuenta memberships activos del tenant (no users globales) — esto es el + // límite real ahora que los users pueden estar en varios tenants. + const currentCount = await prisma.tenantMembership.count({ + where: { tenantId, active: true }, + }); + + if (maxUsers !== -1 && currentCount >= maxUsers) { + throw new Error('Límite de usuarios alcanzado para este plan'); + } + + // Si el email ya existe como user global, agregamos membership en este + // tenant en vez de crear un user nuevo. Cubre el caso "el contador X ya + // trabaja en otra empresa, ahora lo invitan a esta también". + let user = await prisma.user.findUnique({ where: { email: data.email } }); + let tempPassword: string | null = null; + + if (!user) { + tempPassword = randomBytes(4).toString('hex'); + const passwordHash = await bcrypt.hash(tempPassword, 12); + user = await prisma.user.create({ + data: { + email: data.email, + passwordHash, + nombre: data.nombre, + lastTenantId: tenantId, + }, + }); + } + + const rolId = await getRolId(data.role); + + // Membership en este tenant. Si ya existía (caso edge: re-invitación tras + // delete), reactivar. isOwner siempre false (owners se crean por register + // o addTenantToOwner). + await prisma.tenantMembership.upsert({ + where: { userId_tenantId: { userId: user.id, tenantId } }, + update: { rolId, isOwner: false, active: true }, + create: { + userId: user.id, + tenantId, + rolId, + isOwner: false, + active: true, + }, + }); + + // Volvemos a leer la membership para devolver el shape correcto + const membership = await prisma.tenantMembership.findUnique({ + where: { userId_tenantId: { userId: user.id, tenantId } }, + include: MEMBERSHIP_INCLUDE, + }); + + return mapMembershipRow(membership!); +} + +export async function updateUsuario( + tenantId: string, + userId: string, + data: UserUpdate +): Promise { + // Verifica que la membership existe en este tenant antes de actualizar. + const membership = await prisma.tenantMembership.findUnique({ + where: { userId_tenantId: { userId, tenantId } }, + }); + if (!membership) { + throw new Error('El usuario no es miembro de este tenant'); + } + + // El cambio de role es por-tenant (afecta solo la membership) + if (data.role) { + const rolId = await getRolId(data.role); + await prisma.tenantMembership.update({ + where: { userId_tenantId: { userId, tenantId } }, + data: { rolId }, + }); + } + + // active y nombre son globales del user + const userUpdate: any = {}; + if (data.nombre) userUpdate.nombre = data.nombre; + if (data.active !== undefined) userUpdate.active = data.active; + if (Object.keys(userUpdate).length > 0) { + await prisma.user.update({ where: { id: userId }, data: userUpdate }); + } + + const refreshed = await prisma.tenantMembership.findUnique({ + where: { userId_tenantId: { userId, tenantId } }, + include: MEMBERSHIP_INCLUDE, + }); + + return mapMembershipRow(refreshed!); +} + +export async function deleteUsuario(tenantId: string, userId: string): Promise { + // Soft-delete: desactiva la membership. El user sigue existiendo (puede + // tener acceso a otros tenants). Si era su única membership activa, queda + // sin acceso pero no se borra el record. + const result = await prisma.tenantMembership.deleteMany({ + where: { userId, tenantId }, + }); + if (result.count === 0) { + throw new Error('El usuario no es miembro de este tenant'); + } +} + +// ============================================================================ +// Admin global (cross-tenant) +// ============================================================================ + +/** + * Lista todos los memberships del sistema. Cada row es una combinación + * (user, tenant) — un mismo user con N memberships aparece N veces. La UI + * admin global lo presenta así para ser explícita sobre quién tiene acceso + * dónde. + */ +export async function getAllUsuarios(): Promise { + const memberships = await prisma.tenantMembership.findMany({ + where: { active: true }, + include: { + ...MEMBERSHIP_INCLUDE, + tenant: { select: { id: true, nombre: true } }, + }, + orderBy: [{ tenant: { nombre: 'asc' } }, { joinedAt: 'desc' }], + }); + + return memberships.map(m => mapMembershipRow(m, true)); +} + +export async function updateUsuarioGlobal( + userId: string, + data: UserUpdate & { tenantId?: string } +): Promise { + // Si data.tenantId está presente: actualiza el role en esa membership. + // Si data.active está: actualiza User.active globalmente. + const targetTenantId = data.tenantId; + + if (data.role && targetTenantId) { + const rolId = await getRolId(data.role); + await prisma.tenantMembership.update({ + where: { userId_tenantId: { userId, tenantId: targetTenantId } }, + data: { rolId }, + }); + } + + const userUpdate: any = {}; + if (data.nombre) userUpdate.nombre = data.nombre; + if (data.active !== undefined) userUpdate.active = data.active; + if (Object.keys(userUpdate).length > 0) { + await prisma.user.update({ where: { id: userId }, data: userUpdate }); + } + + // Devuelve la primera membership activa del user (o la del tenant target si + // se especificó) para mantener el shape esperado por el caller. + const where = targetTenantId + ? { userId_tenantId: { userId, tenantId: targetTenantId } } + : undefined; + + const m = where + ? await prisma.tenantMembership.findUnique({ + where, + include: { ...MEMBERSHIP_INCLUDE, tenant: { select: { id: true, nombre: true } } }, + }) + : await prisma.tenantMembership.findFirst({ + where: { userId, active: true }, + include: { ...MEMBERSHIP_INCLUDE, tenant: { select: { id: true, nombre: true } } }, + orderBy: { joinedAt: 'asc' }, + }); + + if (!m) throw new Error('Usuario sin memberships activas'); + return mapMembershipRow(m, true); +} + +export async function deleteUsuarioGlobal(userId: string): Promise { + // Hard delete del user — cascade borra todas las memberships, refresh + // tokens, password reset tokens, platform roles, etc. + await prisma.user.delete({ where: { id: userId } }); +} diff --git a/apps/api/src/utils/audit.ts b/apps/api/src/utils/audit.ts new file mode 100644 index 0000000..e1efa11 --- /dev/null +++ b/apps/api/src/utils/audit.ts @@ -0,0 +1,72 @@ +/** + * Audit log — registro de acciones críticas. + * + * Convención fire-and-forget: auditar NUNCA debe romper la acción principal. + * Si falla el INSERT, se logea en consola y la ejecución continúa. + * + * Acciones nombradas con convención `domain.verb`: + * - user.login, user.logout, user.password_changed + * - tenant.created, tenant.deleted + * - subscription.created, subscription.cancelled, subscription.reactivated, + * subscription.plan_changed + * - trial.started + * - price.updated + * - invoice.emitted_auto, invoice.emitted_manual, invoice.cancelled + * - fiel.uploaded, fiel.deleted + * - payment.recorded, payment.marked_paid_manually + * + * No logear GETs normales (dashboard, listar CFDIs) — demasiado volumen y sin + * valor auditable. Log solo write operations con implicación fiscal/financiera. + */ +import type { Request } from 'express'; +import { prisma } from '../config/database.js'; + +export interface AuditLogParams { + userId?: string; + tenantId?: string; + action: string; + entityType?: string; + entityId?: string; + metadata?: Record; +} + +export async function auditLog(params: AuditLogParams): Promise { + try { + await prisma.auditLog.create({ + data: { + userId: params.userId, + tenantId: params.tenantId, + action: params.action, + entityType: params.entityType, + entityId: params.entityId, + metadata: (params.metadata ?? undefined) as any, + }, + }); + } catch (error: any) { + console.error('[Audit] Falló registrar evento:', params.action, error.message || error); + } +} + +/** + * Helper para usar desde controllers — extrae user + tenant del `req` y enriquece + * metadata con IP y user-agent automáticamente. + */ +export async function auditFromReq( + req: Request, + action: string, + extra?: Omit & { metadata?: Record }, +): Promise { + const baseMetadata = { + ip: req.ip, + userAgent: req.get('user-agent'), + }; + + await auditLog({ + userId: req.user?.userId, + tenantId: extra?.tenantId ?? req.user?.tenantId, + entityType: extra?.entityType, + entityId: extra?.entityId, + metadata: { ...baseMetadata, ...(extra?.metadata ?? {}) }, + action, + }); +} diff --git a/apps/api/src/utils/contribuyente-context.ts b/apps/api/src/utils/contribuyente-context.ts new file mode 100644 index 0000000..49be032 --- /dev/null +++ b/apps/api/src/utils/contribuyente-context.ts @@ -0,0 +1,91 @@ +import type { Pool } from 'pg'; +import { prisma } from '../config/database.js'; + +export interface ContribuyenteContext { + contribuyenteId: string | null; + rfc: string; + rfcLength: number; + /** + * DEPRECATED — filtro genérico `AND (contribuyente_id = X OR rfc_emisor=X_RFC OR rfc_receptor=X_RFC)`. + * Útil solo cuando el query no distingue lado (emisor vs receptor). + * Para queries con semantic por lado, usa `esEmisor` / `esReceptor`. + */ + contribFilter: string; + /** + * Fragmento SQL que identifica al contribuyente como **emisor** del CFDI. + * Reemplaza al par `type = 'EMITIDO' AND contribuyente_id = X`. Usa RFC + * como fuente de verdad (el `type` y `contribuyente_id` en BD pueden ser + * inconsistentes cuando dos contribuyentes del mismo tenant se facturan + * entre sí — el RFC en la posición emisor/receptor no tiene ambigüedad). + * Sin el `AND` prefijo — úsalo dentro de expresión compuesta. + */ + esEmisor: string; + /** Similar a `esEmisor`, identifica al contribuyente como **receptor**. */ + esReceptor: string; +} + +/** + * Resolves the RFC and builds a CFDI contribuyente filter fragment. + * - If contribuyenteId is provided: RFC comes from contribuyentes table (tenant BD), + * filter restricts CFDIs to that contribuyente_id. + * - If not: RFC comes from Tenant (central BD), no contribuyente filter (Horux360 compat). + */ +export async function resolveContribuyenteContext( + pool: Pool, + tenantId: string, + contribuyenteId?: string | null, +): Promise { + if (contribuyenteId) { + // Sanitize: UUIDs are only hex + hyphens + const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + const { rows } = await pool.query( + 'SELECT rfc FROM contribuyentes WHERE entidad_id = $1', + [safeId], + ); + const rfc: string = rows[0]?.rfc || ''; + const safeRfc = rfc.replace(/[^A-Z0-9]/gi, '').toUpperCase(); + + // Filtro genérico (deprecated): contribuyente_id directo O RFC en + // cualquier lado. Sigue disponible para queries legacy que no distinguen + // lado. Los callers modernos deberían usar esEmisor/esReceptor. + let contribFilter = ''; + if (safeId && safeRfc) { + contribFilter = `AND (contribuyente_id = '${safeId}' OR UPPER(rfc_emisor) = '${safeRfc}' OR UPPER(rfc_receptor) = '${safeRfc}')`; + } else if (safeId) { + contribFilter = `AND contribuyente_id = '${safeId}'`; + } + + // esEmisor / esReceptor: fuente de verdad por posición del RFC. + // Reemplazan al par `type = 'EMITIDO/RECIBIDO' AND contribuyente_id = X`. + const esEmisor = safeRfc ? `UPPER(rfc_emisor) = '${safeRfc}'` : `1=0`; + const esReceptor = safeRfc ? `UPPER(rfc_receptor) = '${safeRfc}'` : `1=0`; + + return { + contribuyenteId: safeId, + rfc, + rfcLength: rfc.length, + contribFilter, + esEmisor, + esReceptor, + }; + } + + // Horux360 mode: no contribuyente → RFC from central BD + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { rfc: true }, + }); + const rfc = tenant?.rfc || ''; + const safeRfc = rfc.replace(/[^A-Z0-9]/gi, '').toUpperCase(); + // Para Horux 360 single-tenant, el RFC del tenant actúa como el contrib. + const esEmisor = safeRfc ? `UPPER(rfc_emisor) = '${safeRfc}'` : `type = 'EMITIDO'`; + const esReceptor = safeRfc ? `UPPER(rfc_receptor) = '${safeRfc}'` : `type = 'RECIBIDO'`; + return { + contribuyenteId: null, + rfc, + rfcLength: rfc.length, + contribFilter: '', + esEmisor, + esReceptor, + }; +} diff --git a/apps/api/src/utils/entidades-visibles.ts b/apps/api/src/utils/entidades-visibles.ts new file mode 100644 index 0000000..d191846 --- /dev/null +++ b/apps/api/src/utils/entidades-visibles.ts @@ -0,0 +1,68 @@ +import type { Pool } from 'pg'; +import type { Role } from '@horux/shared'; + +export async function getEntidadesVisibles(pool: Pool, userId: string, role: Role): Promise { + let query: string; + let params: unknown[]; + + switch (role) { + case 'owner': + case 'cfo': + case 'contador': + case 'visor': + // Full access to all active entities + query = 'SELECT id FROM entidades_gestionadas WHERE active = true'; + params = []; + break; + + case 'supervisor': + // Entities assigned to supervisor: via entidades_gestionadas.supervisor_user_id + // OR via cartera_entidades in carteras assigned to this supervisor + query = ` + SELECT DISTINCT id FROM ( + SELECT id FROM entidades_gestionadas WHERE supervisor_user_id = $1 AND active = true + UNION + SELECT ce.entidad_id AS id + FROM cartera_entidades ce + JOIN carteras c ON c.id = ce.cartera_id AND c.parent_id IS NULL + JOIN entidades_gestionadas e ON e.id = ce.entidad_id + WHERE c.supervisor_user_id = $1 AND e.active = true + ) sub + `; + params = [userId]; + break; + + case 'auxiliar': + // Entities from subcarteras assigned to this auxiliar + // OR from legacy cartera_auxiliares (backward compat) + query = ` + SELECT DISTINCT id FROM ( + SELECT ce.entidad_id AS id + FROM cartera_entidades ce + JOIN carteras c ON c.id = ce.cartera_id + JOIN entidades_gestionadas e ON e.id = ce.entidad_id + WHERE c.auxiliar_user_id = $1 AND e.active = true + UNION + SELECT ce.entidad_id AS id + FROM cartera_entidades ce + JOIN cartera_auxiliares ca ON ca.cartera_id = ce.cartera_id + JOIN entidades_gestionadas e ON e.id = ce.entidad_id + WHERE ca.auxiliar_user_id = $1 AND e.active = true + ) sub + `; + params = [userId]; + break; + + case 'cliente': + // Only entities explicitly granted via cliente_accesos + query = 'SELECT entidad_id AS id FROM cliente_accesos WHERE user_id = $1'; + params = [userId]; + break; + + default: + return []; + } + + const { rows } = await pool.query(query, params); + return rows.map(r => r.id); +} diff --git a/apps/api/src/utils/errors.ts b/apps/api/src/utils/errors.ts new file mode 100644 index 0000000..373dafd --- /dev/null +++ b/apps/api/src/utils/errors.ts @@ -0,0 +1,10 @@ +export class AppError extends Error { + constructor( + public statusCode: number, + public message: string, + public isOperational = true + ) { + super(message); + Object.setPrototypeOf(this, AppError.prototype); + } +} diff --git a/apps/api/src/utils/global-admin.ts b/apps/api/src/utils/global-admin.ts new file mode 100644 index 0000000..8102e86 --- /dev/null +++ b/apps/api/src/utils/global-admin.ts @@ -0,0 +1,14 @@ +/** + * DEPRECATED — shim de compatibilidad. La lógica vive ahora en `platform-admin.ts`. + * + * `isGlobalAdmin` ahora resuelve vía tabla `user_platform_roles` (rol + * `platform_admin`) en lugar del hardcode `GLOBAL_ADMIN_RFC`. El check por RFC + * queda como fallback por si la tabla aún no está poblada (escenario pre-seed). + * + * Código nuevo debería usar: + * - `hasPlatformRole(userId, 'platform_admin')` para el equivalente directo + * - `canManageTenants(userId)`, `canEditPrices(userId)`, `canEmitInvoicesManual(userId)` + * para controles granulares + * - `isPlatformStaff(userId)` para "cualquier staff interno" + */ +export { isGlobalAdmin } from './platform-admin.js'; diff --git a/apps/api/src/utils/memberships.ts b/apps/api/src/utils/memberships.ts new file mode 100644 index 0000000..36324e7 --- /dev/null +++ b/apps/api/src/utils/memberships.ts @@ -0,0 +1,103 @@ +import { prisma } from '../config/database.js'; +import type { Role, TenantMembership } from '@horux/shared'; + +/** + * Lista los tenants a los que el user tiene acceso vía la tabla + * `tenant_memberships`. Usado por /auth/login, /auth/me, /auth/switch-tenant + * para poblar el array `tenants[]` del UserInfo. + * + * Durante la transición (fase 2), si el user todavía no tiene memberships + * (caso borde post-deploy antes del backfill), devuelve un array con su + * tenant "default" (User.tenantId) para no romper UX. + */ +export async function getUserTenants(userId: string): Promise { + const memberships = await prisma.tenantMembership.findMany({ + where: { userId, active: true }, + include: { + tenant: { select: { id: true, nombre: true, rfc: true, plan: true, active: true } }, + rol: { select: { nombre: true } }, + }, + orderBy: { joinedAt: 'asc' }, + }); + + return memberships + .filter(m => m.tenant.active) // Esconde memberships de tenants desactivados + .map(m => ({ + id: m.tenant.id, + nombre: m.tenant.nombre, + rfc: m.tenant.rfc, + plan: m.tenant.plan, + role: m.rol.nombre as Role, + isOwner: m.isOwner, + })); +} + +/** + * Verifica que el user tiene membership activo en el tenant dado. Usado por + * /auth/switch-tenant antes de emitir un JWT con ese tenantId. + */ +/** + * ¿El user es owner en al menos un tenant activo? Usado como gate en + * POST /tenants/mine (agregar RFC nuevo) — solo users que ya son owner en + * alguna empresa pueden registrar otra más. + */ +export async function isOwnerSomewhere(userId: string): Promise { + const row = await prisma.tenantMembership.findFirst({ + where: { userId, isOwner: true, active: true, tenant: { active: true } }, + select: { id: true }, + }); + return !!row; +} + +/** + * Devuelve el email del owner del tenant (primero por joinedAt). Usado por + * subscription.service para enviar notificaciones de pago/cancelación al + * dueño. Si el tenant no tiene owner activo, retorna null. + */ +export async function getTenantOwnerEmail(tenantId: string): Promise { + const m = await prisma.tenantMembership.findFirst({ + where: { tenantId, isOwner: true, active: true }, + include: { user: { select: { email: true } } }, + orderBy: { joinedAt: 'asc' }, + }); + return m?.user.email ?? null; +} + +/** + * Devuelve los emails de TODOS los owners activos del tenant (puede haber + * más de uno). Usado para notificaciones broadcast (upload de documentos, + * etc.) donde queremos avisar a todos los dueños. + */ +export async function getTenantOwnerEmails(tenantId: string): Promise { + const ms = await prisma.tenantMembership.findMany({ + where: { tenantId, isOwner: true, active: true }, + include: { user: { select: { email: true, active: true } } }, + orderBy: { joinedAt: 'asc' }, + }); + return ms + .filter(m => m.user.active) + .map(m => m.user.email); +} + +/** + * Dado un userId, retorna su email. Null si el user no existe o está inactivo. + */ +export async function getUserEmailById(userId: string): Promise { + const u = await prisma.user.findUnique({ + where: { id: userId }, + select: { email: true, active: true }, + }); + return u && u.active ? u.email : null; +} + +export async function verifyMembership(userId: string, tenantId: string): Promise<{ + rolNombre: Role; + isOwner: boolean; +} | null> { + const m = await prisma.tenantMembership.findFirst({ + where: { userId, tenantId, active: true }, + include: { rol: { select: { nombre: true } }, tenant: { select: { active: true } } }, + }); + if (!m || !m.tenant.active) return null; + return { rolNombre: m.rol.nombre as Role, isOwner: m.isOwner }; +} diff --git a/apps/api/src/utils/metricas-cache.ts b/apps/api/src/utils/metricas-cache.ts new file mode 100644 index 0000000..ab9d83b --- /dev/null +++ b/apps/api/src/utils/metricas-cache.ts @@ -0,0 +1,40 @@ +export interface CacheRange { + contribuyenteId: string; + startDate: string; // YYYY-MM-01 (día 1 del mes inicial) + endDate: string; // YYYY-MM-01 (día 1 del mes final, para BETWEEN contra make_date) +} + +/** + * Decide si un rango de fechas es elegible para leer de `metricas_mensuales` + * (read-through cache de Tanda B). Requisitos: + * - `contribuyenteId` presente (la tabla solo tiene datos por contribuyente) + * - `conciliacion` desactivada (la tabla guarda flujo normal, no id_conciliacion) + * - `fechaFin` antes del primer día del año actual (años cerrados) + * - `fechaInicio` es día 1 del mes; `fechaFin` es último día del mes + * + * Retorna `null` si no califica — el caller debe caer al path on-the-fly. + */ +export function planCache( + fechaInicio: string, + fechaFin: string, + conciliacion: boolean | undefined, + contribuyenteId: string | null | undefined, +): CacheRange | null { + // Escape hatch para validación: forzar el path on-the-fly aunque el rango + // califique. Útil en `validate-metricas.ts` para comparar cache vs raw. + if (process.env.METRICAS_BYPASS_CACHE === '1') return null; + if (!contribuyenteId || conciliacion) return null; + const fi = new Date(fechaInicio + 'T00:00:00Z'); + const ff = new Date(fechaFin + 'T00:00:00Z'); + if (isNaN(fi.getTime()) || isNaN(ff.getTime())) return null; + const currentYearStart = new Date(Date.UTC(new Date().getUTCFullYear(), 0, 1)); + if (ff >= currentYearStart) return null; + if (fi.getUTCDate() !== 1) return null; + const lastDay = new Date(Date.UTC(ff.getUTCFullYear(), ff.getUTCMonth() + 1, 0)).getUTCDate(); + if (ff.getUTCDate() !== lastDay) return null; + const safe = contribuyenteId.replace(/[^a-f0-9-]/gi, ''); + if (!safe) return null; + const startDate = `${fi.getUTCFullYear()}-${String(fi.getUTCMonth() + 1).padStart(2, '0')}-01`; + const endDate = `${ff.getUTCFullYear()}-${String(ff.getUTCMonth() + 1).padStart(2, '0')}-01`; + return { contribuyenteId: safe, startDate, endDate }; +} diff --git a/apps/api/src/utils/platform-admin.ts b/apps/api/src/utils/platform-admin.ts new file mode 100644 index 0000000..dfa531a --- /dev/null +++ b/apps/api/src/utils/platform-admin.ts @@ -0,0 +1,133 @@ +/** + * Helpers de autorización basados en **roles de plataforma** (staff interno de + * Horux 360). Ortogonal al rol per-tenant (owner, cfo, contador, auxiliar, visor). + * + * Migración desde el modelo anterior: + * - Antes: `isGlobalAdmin(tenantId, role)` checaba si el user era owner del + * tenant con RFC HTS240708LJA. Una sola persona admin global. + * - Ahora: `isGlobalAdmin()` sigue existiendo como alias de `hasPlatformRole('platform_admin')`, + * pero múltiples users pueden tener ese rol. + * - Compatibilidad: si la tabla `user_platform_roles` está vacía (pre-migración), + * cae al check por RFC como fallback. Post-seed, los owners del tenant dueño + * ya están registrados como `platform_admin` y el check funciona sin fallback. + * + * Cache in-memory 5min por (userId, role) para evitar hit a BD en cada request. + */ +import { prisma } from '../config/database.js'; +import { GLOBAL_ADMIN_RFC } from '@horux/shared'; + +export type PlatformRole = 'platform_admin' | 'platform_ti' | 'platform_support' | 'platform_sales' | 'platform_finance'; + +/** Roles superset — implican todos los demás. */ +const SUPERSET_ROLES: PlatformRole[] = ['platform_admin', 'platform_ti']; + +function hasSuperset(roles: Set): boolean { + return SUPERSET_ROLES.some(r => roles.has(r)); +} + +interface CacheEntry { + roles: Set; + expires: number; +} + +const cache = new Map(); +const CACHE_TTL = 5 * 60 * 1000; // 5 minutos + +export function invalidatePlatformRolesCache(userId: string) { + cache.delete(userId); +} + +/** + * Carga los roles de plataforma de un user. Hit a BD con cache de 5 min. + * Si el user no tiene rows en `user_platform_roles`, devuelve Set vacío. + */ +async function loadPlatformRoles(userId: string): Promise> { + const cached = cache.get(userId); + if (cached && cached.expires > Date.now()) return cached.roles; + + const rows = await prisma.userPlatformRole.findMany({ + where: { userId }, + select: { role: true }, + }); + const roles = new Set(rows.map(r => r.role as PlatformRole)); + cache.set(userId, { roles, expires: Date.now() + CACHE_TTL }); + return roles; +} + +/** + * ¿El user tiene el rol de plataforma indicado? + * `platform_admin` automáticamente implica TODOS los otros roles (superrol). + */ +export async function hasPlatformRole(userId: string | undefined, role: PlatformRole): Promise { + if (!userId) return false; + const roles = await loadPlatformRoles(userId); + if (hasSuperset(roles)) return true; + return roles.has(role); +} + +/** + * ¿El user tiene alguno de los roles indicados? + */ +export async function hasAnyPlatformRole(userId: string | undefined, ...allowedRoles: PlatformRole[]): Promise { + if (!userId) return false; + const roles = await loadPlatformRoles(userId); + if (hasSuperset(roles)) return true; + return allowedRoles.some(r => roles.has(r)); +} + +/** Atajos granulares — usar en middlewares o guards de endpoints */ +export const canManageTenants = (userId?: string) => + hasAnyPlatformRole(userId, 'platform_admin', 'platform_sales', 'platform_support'); + +export const canEditPrices = (userId?: string) => + hasAnyPlatformRole(userId, 'platform_admin', 'platform_finance'); + +export const canEmitInvoicesManual = (userId?: string) => + hasAnyPlatformRole(userId, 'platform_admin', 'platform_finance'); + +export const isPlatformStaff = async (userId?: string) => { + if (!userId) return false; + const roles = await loadPlatformRoles(userId); + return roles.size > 0; +}; + +/** + * Obtiene los roles actuales del user (para incluir en JWT al login). + */ +export async function getPlatformRoles(userId: string): Promise { + const roles = await loadPlatformRoles(userId); + return Array.from(roles); +} + +/** + * Compatibilidad con el código legacy que usa `isGlobalAdmin(tenantId, role)`. + * Ahora resuelve vía tabla `user_platform_roles` → rol `platform_admin`. + * + * Fallback: si no hay rows en la tabla para el user que está intentando, pero es + * owner del tenant HTS240708LJA, se considera platform_admin (cubre el escenario + * post-deploy pre-seed). + */ +export async function isGlobalAdmin(tenantId: string, role: string): Promise { + // Las firmas viejas no tienen userId disponible. Lo resolvemos buscando el user + // que matchea tenantId + rol 'owner'. Para evitar ese hit extra, la preferencia + // es usar `hasPlatformRole(req.user.userId, 'platform_admin')` en código nuevo. + if (role !== 'owner') return false; + + // Primer intento: ¿hay users con rol superset (admin o TI) en este tenant? + // Vía memberships: el user tiene un superset role Y es miembro del tenant. + const adminRow = await prisma.userPlatformRole.findFirst({ + where: { + role: { in: SUPERSET_ROLES }, + user: { memberships: { some: { tenantId, active: true } } }, + }, + select: { userId: true }, + }); + if (adminRow) return true; + + // Fallback: tenant con RFC admin global (compat antes del backfill) + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { rfc: true }, + }); + return tenant?.rfc === GLOBAL_ADMIN_RFC; +} diff --git a/apps/api/src/utils/saldo.ts b/apps/api/src/utils/saldo.ts new file mode 100644 index 0000000..b8b3241 --- /dev/null +++ b/apps/api/src/utils/saldo.ts @@ -0,0 +1,136 @@ +import type { Pool } from 'pg'; + +/** + * SQL que computa el saldo pendiente de un CFDI I PPD. + * + * Fórmula (misma que §13 del doc Horux_despacho): + * saldo = total_mxn + * − Σ P.monto_pago_mxn (complementos P que referencian el UUID) + * − Σ E.total_mxn (NC donde cfdi_tipo_relacion <> '07') + * − anticipo_aplicado (si el CFDI es I/07, suma de totales de + * los UUIDs en cfdis_relacionados) + * + * NO se clamp a 0: un saldo negativo indica over-pago o over-aplicación + * (señal útil). Los filtros downstream pueden excluir saldos < 0.01 si + * solo quieren ver pendientes. + * + * `alias` = alias de la tabla `cfdis` en la query (ej. 'c', 'cfdis'). + */ +export function saldoComputadoExpr(alias: string): string { + const c = alias; + return `( + COALESCE(${c}.total_mxn, 0) + - COALESCE(( + SELECT SUM(COALESCE(p.monto_pago_mxn, 0)) + FROM cfdis p + WHERE p.tipo_comprobante = 'P' + AND LOWER(COALESCE(p.uuid_relacionado, '')) LIKE '%' || LOWER(${c}.uuid) || '%' + AND p.status NOT IN ('Cancelado', '0') + ), 0) + - COALESCE(( + SELECT SUM(COALESCE(e.total_mxn, 0)) + FROM cfdis e + WHERE e.tipo_comprobante = 'E' + AND COALESCE(e.cfdi_tipo_relacion, '') <> '07' + AND e.cfdis_relacionados IS NOT NULL + AND LOWER(${c}.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|')) + AND e.status NOT IN ('Cancelado', '0') + ), 0) + - CASE WHEN ${c}.cfdi_tipo_relacion = '07' AND ${c}.cfdis_relacionados IS NOT NULL THEN + COALESCE(( + SELECT SUM(COALESCE(a.total_mxn, 0)) + FROM cfdis a + WHERE LOWER(a.uuid) = ANY(string_to_array(LOWER(${c}.cfdis_relacionados), '|')) + AND a.status NOT IN ('Cancelado', '0') + ), 0) + ELSE 0 END + )`; +} + +/** + * Recomputa `saldo_pendiente_mxn` para un conjunto de UUIDs de CFDIs. + * Solo aplica a I PPD vigentes (otros tipos no tienen saldo pendiente + * en el sentido de cuentas por cobrar/pagar). Idempotente. + * + * UUIDs se normalizan a lowercase para matchear independiente del case + * en que vienen (XML suele traer uppercase, parser local normaliza). + */ +export async function recomputarSaldoPendiente( + pool: Pool, + uuids: string[], +): Promise { + if (uuids.length === 0) return 0; + const expr = saldoComputadoExpr('c'); + const lowered = uuids.map(u => u.toLowerCase()); + const { rowCount } = await pool.query( + `UPDATE cfdis c + SET saldo_pendiente_mxn = ${expr} + WHERE LOWER(c.uuid) = ANY($1::text[]) + AND c.tipo_comprobante = 'I' + AND c.metodo_pago = 'PPD' + AND c.status NOT IN ('Cancelado', '0')`, + [lowered], + ); + return rowCount ?? 0; +} + +/** + * Recomputa saldo pendiente para TODAS las I PPD vigentes del tenant. + * Usado por el backfill one-shot y por validación/debug. + */ +export async function recomputarSaldoTodos(pool: Pool): Promise { + const expr = saldoComputadoExpr('c'); + const { rowCount } = await pool.query( + `UPDATE cfdis c + SET saldo_pendiente_mxn = ${expr} + WHERE c.tipo_comprobante = 'I' + AND c.metodo_pago = 'PPD' + AND c.status NOT IN ('Cancelado', '0')`, + ); + return rowCount ?? 0; +} + +/** + * Dado un CFDI recién insertado/actualizado, devuelve el set de UUIDs + * cuya saldo_pendiente_mxn debe recomputarse. Centraliza la lógica de + * "qué filas tocar" para cada tipo: + * + * - I PPD: su propio UUID (saldo inicial, considera anticipo si es I/07) + * - P: los UUIDs referenciados en uuid_relacionado (pueden ser pipe-separated) + * - E no-07: los UUIDs en cfdis_relacionados + * - Otros tipos: no afectan saldos + */ +export function uuidsAfectadosPorCfdi(cfdi: { + uuid: string; + tipoComprobante: string | null; + metodoPago?: string | null; + cfdiTipoRelacion?: string | null; + uuidRelacionado?: string | null; + cfdisRelacionados?: string | null; +}): string[] { + const result: string[] = []; + + if (cfdi.tipoComprobante === 'I' && cfdi.metodoPago === 'PPD' && cfdi.uuid) { + result.push(cfdi.uuid.toLowerCase()); + } + + if (cfdi.tipoComprobante === 'P' && cfdi.uuidRelacionado) { + for (const ref of cfdi.uuidRelacionado.split('|')) { + const clean = ref.trim().toLowerCase(); + if (clean) result.push(clean); + } + } + + if ( + cfdi.tipoComprobante === 'E' && + cfdi.cfdiTipoRelacion !== '07' && + cfdi.cfdisRelacionados + ) { + for (const ref of cfdi.cfdisRelacionados.split('|')) { + const clean = ref.trim().toLowerCase(); + if (clean) result.push(clean); + } + } + + return result; +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..1170081 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..0ce6b13 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=http://localhost:4000/api diff --git a/apps/web/app/(auth)/forgot-password/page.tsx b/apps/web/app/(auth)/forgot-password/page.tsx new file mode 100644 index 0000000..5cd1653 --- /dev/null +++ b/apps/web/app/(auth)/forgot-password/page.tsx @@ -0,0 +1,118 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import Image from 'next/image'; +import { Button, Input, Label, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@horux/shared-ui'; +import { requestPasswordReset } from '@/lib/api/auth'; +import { Mail, CheckCircle2 } from 'lucide-react'; + +export default function ForgotPasswordPage() { + const [isLoading, setIsLoading] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(''); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setIsLoading(true); + setError(''); + + const formData = new FormData(e.currentTarget); + const email = formData.get('email') as string; + + try { + await requestPasswordReset(email); + setSubmitted(true); + } catch (err: any) { + // Rate limit u otro error explícito + setError(err.response?.data?.message || 'Error al enviar el enlace. Intenta más tarde.'); + } finally { + setIsLoading(false); + } + } + + if (submitted) { + return ( +
+ + +
+
+ +
+
+ Revisa tu correo + + Si el email que ingresaste está registrado, recibirás un enlace para restablecer tu contraseña. + +
+ +
+

¿No recibiste el correo?

+
    +
  • Revisa tu carpeta de spam o promociones
  • +
  • El enlace expira en 1 hora — si pasó más tiempo, vuelve a solicitar
  • +
  • Asegúrate de haber escrito bien tu email
  • +
+
+
+ + + + Volver al login + + +
+
+ ); + } + + return ( +
+ + +
+ Horux360 +
+ + + Recuperar contraseña + + + Ingresa tu email y te enviaremos un enlace para crear una nueva contraseña. + +
+
+ + {error && ( +
+ {error} +
+ )} +
+ + +
+
+ + + + Volver al login + + +
+
+
+ ); +} diff --git a/apps/web/app/(auth)/layout.tsx b/apps/web/app/(auth)/layout.tsx new file mode 100644 index 0000000..9e3abba --- /dev/null +++ b/apps/web/app/(auth)/layout.tsx @@ -0,0 +1,11 @@ +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx new file mode 100644 index 0000000..1bbb85d --- /dev/null +++ b/apps/web/app/(auth)/login/page.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import Image from 'next/image'; +import { Button, Input, Label, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@horux/shared-ui'; +import { login } from '@/lib/api/auth'; +import { useAuthStore } from '@/stores/auth-store'; +import { isGlobalAdminRfc } from '@horux/shared'; +import { shouldShowOnboarding } from '@/lib/onboarding'; + +export default function LoginPage() { + const router = useRouter(); + const { setUser, setTokens } = useAuthStore(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setIsLoading(true); + setError(''); + + const formData = new FormData(e.currentTarget); + const email = formData.get('email') as string; + const password = formData.get('password') as string; + + try { + const response = await login({ email, password }); + setTokens(response.accessToken, response.refreshToken); + setUser(response.user); + + const userRole = response.user?.role; + // Admin global aterriza directo en `/clientes` — su home natural es la + // gestión de tenants, no el dashboard operativo del despacho. + const platformRoles = (response.user as { platformRoles?: string[] }).platformRoles; + const isGlobalAdmin = isGlobalAdminRfc(response.user?.tenantRfc, userRole, platformRoles); + if (isGlobalAdmin) { + router.push('/clientes'); + } else if (userRole === 'cliente' || userRole === 'auxiliar' || userRole === 'supervisor') { + // Clients and roles without onboarding go straight to dashboard + router.push('/dashboard'); + } else { + // Owner/CFO/Contador: onboarding hasta 4 logins o hasta que el user + // complete los pasos requeridos (lo que pase primero). + router.push(shouldShowOnboarding(response.user) ? '/onboarding' : '/dashboard'); + } + } catch (err: any) { + setError(err.response?.data?.message || 'Error al iniciar sesión'); + } finally { + setIsLoading(false); + } + } + + return ( +
+ + +
+ Horux360 +
+ Iniciar Sesión + + Ingresa tus credenciales para acceder a tu cuenta + +
+
+ + {error && ( +
+ {error} +
+ )} +
+ + +
+
+
+ + + ¿Olvidaste tu contraseña? + +
+ +
+
+ + +

+ ¿No tienes cuenta?{' '} + + Regístrate + +

+
+
+
+
+ ); +} diff --git a/apps/web/app/(auth)/register-despacho/page.tsx b/apps/web/app/(auth)/register-despacho/page.tsx new file mode 100644 index 0000000..8297f24 --- /dev/null +++ b/apps/web/app/(auth)/register-despacho/page.tsx @@ -0,0 +1,415 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { Button, Input, Label, Card, CardContent, CardHeader, CardTitle, cn } from '@horux/shared-ui'; +import { useAuthStore } from '@/stores/auth-store'; +import { apiClient } from '@/lib/api/client'; +import { CheckCircle2, Server, Cloud, ArrowLeft, Clock, Building, Sparkles } from 'lucide-react'; + +type VerticalProfile = 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA'; +type PlanType = 'trial' | 'mi_empresa' | 'mi_empresa_plus' | 'business_control' | 'business_cloud'; + +export default function RegisterDespachoPage() { + const router = useRouter(); + const { setUser, setTokens } = useAuthStore(); + const [step, setStep] = useState(1); + const [verticalProfile, setVerticalProfile] = useState(null); + const [selectedPlan, setSelectedPlan] = useState(null); + // Default 'annual' — sesgo intencional al cash-flow del negocio (10 meses + // = 17% descuento para el cliente, año completo cobrado upfront para nosotros). + const [billingFrequency, setBillingFrequency] = useState<'monthly' | 'annual'>('annual'); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [form, setForm] = useState({ + despachoNombre: '', + ownerNombre: '', + ownerEmail: '', + ownerPassword: '', + acceptedTerms: false, + }); + + const handleChange = (field: string) => (e: React.ChangeEvent) => { + setForm((prev) => ({ ...prev, [field]: e.target.value })); + setError(''); + }; + + const handleSubmit = async () => { + if (!form.acceptedTerms) { setError('Debes aceptar los términos y condiciones'); return; } + if (!verticalProfile || !selectedPlan) { setError('Completa todos los pasos'); return; } + setLoading(true); + setError(''); + try { + const { data } = await apiClient.post('/despachos/signup', { + despacho: { + nombre: form.despachoNombre, + verticalProfile, + plan: selectedPlan, + // Solo mi_empresa(+) acepta monthly; el backend ignora frequency + // para los demás planes. Mandamos siempre el state para coherencia. + frequency: billingFrequency, + }, + owner: { + nombre: form.ownerNombre, + email: form.ownerEmail, + password: form.ownerPassword, + }, + }); + setTokens(data.accessToken, data.refreshToken); + setUser(data.user); + + // If paid plan with payment URL, redirect to MercadoPago + if (data.paymentUrl) { + window.location.href = data.paymentUrl; + } else { + router.push('/onboarding'); + } + } catch (err: any) { + setError(err.response?.data?.message || 'Error al registrar el despacho'); + setStep(1); + } finally { + setLoading(false); + } + }; + + // =================== STEP 1: Registration Form =================== + if (step === 1) { + const canProceed = form.despachoNombre && form.ownerNombre && form.ownerEmail && form.ownerPassword.length >= 10 && form.acceptedTerms; + + return ( +
+ + +
+ 1 + + 2 + + 3 +
+ Crea tu cuenta +

Plataforma para despachos profesionales

+
+ +
+
+
+
+
+
+
+
+
+ setForm((p) => ({ ...p, acceptedTerms: e.target.checked }))} className="mt-1" /> + +
+ {error &&

{error}

} + +

+ ¿Ya tienes cuenta? Inicia sesión +

+
+
+
+ ); + } + + // =================== STEP 2: Vertical Selection =================== + if (step === 2) { + return ( +
+
+
+
+ + + 2 + + 3 +
+

¿Qué tipo de despacho eres?

+

Selecciona tu área profesional

+
+
+ +
+
⚖️
+

Jurídico

+

Próximamente

+
+
+
🏗️
+

Arquitectura

+

Próximamente

+
+
+ +
+
+ ); + } + + // =================== STEP 3: Subscription Selection =================== + return ( +
+
+
+
+ + + + + 3 +
+

Elige tu plan

+

Empieza con el trial gratuito de 30 días o contrata un plan directo.

+
+ + {/* Toggle facturación mensual / anual (afecta solo Mi Empresa y Mi Empresa+) */} +
+
+ + +
+
+ +
+ {/* Trial Gratuito */} + setSelectedPlan('trial')} + > + +
+ +
+ Trial Gratuito +

Prueba sin compromiso

+
+ +
+
$0
+

30 días

+

Sin tarjeta

+
+
+
Hasta 3 RFCs
+
1 usuario
+
Todas las funcionalidades
+
BD en la nube
+
+
+
+ + {/* Mi Empresa */} + setSelectedPlan('mi_empresa')} + > + +
+ +
+ Mi Empresa +

Para 1 contribuyente

+
+ +
+ {billingFrequency === 'annual' ? ( + <> +
$5,800
+

por año

+

Equivale a 10 meses

+ + ) : ( + <> +
$580
+

mensual

+

o $5,800/año (10 meses)

+ + )} +
+
+
1 RFC
+
3 usuarios
+
50 timbres/mes
+
BD en la nube
+
+
+
+ + {/* Mi Empresa + */} + setSelectedPlan('mi_empresa_plus')} + > + +
+ +
+ Mi Empresa + +

Con IA + API

+
+ +
+ {billingFrequency === 'annual' ? ( + <> +
$9,000
+

por año

+

Equivale a 10 meses

+ + ) : ( + <> +
$900
+

mensual

+

o $9,000/año (10 meses)

+ + )} +
+
+
Todo Mi Empresa
+
Lolita IA Fiscal
+
API de integración
+
SAT incremental
+
+
+
+ + {/* Business Control */} + setSelectedPlan('business_control')} + > +
+ Más popular +
+ +
+ +
+ Business Control +

Despachos contables

+
+ +
+
$25,850
+

por año (IVA inc.)

+

+ $45/mes por RFC extra

+
+
+
100 RFCs incluidos
+
Usuarios ilimitados
+
BD en tu servidor
+
Servidor backup
+
API de integración
+
SAT incremental
+
+
+
+ + {/* Enterprise (business_cloud) */} + setSelectedPlan('business_cloud')} + > + +
+ +
+ Enterprise +

Despachos de alto volumen

+
+ +
+
$43,000
+

por año (IVA inc.)

+

+ $45/mes por RFC extra

+
+
+
100 RFCs incluidos
+
3M CFDIs por contribuyente
+
Usuarios ilimitados
+
BD en tu servidor
+
Servidor backup
+
SAT incremental + API
+
+
+
+
+ + {error &&

{error}

} + +
+ + +
+
+
+ ); +} diff --git a/apps/web/app/(auth)/register/page.tsx b/apps/web/app/(auth)/register/page.tsx new file mode 100644 index 0000000..03cb861 --- /dev/null +++ b/apps/web/app/(auth)/register/page.tsx @@ -0,0 +1,145 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { Button, Input, Label, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@horux/shared-ui'; +import { register } from '@/lib/api/auth'; +import { useAuthStore } from '@/stores/auth-store'; + +export default function RegisterPage() { + const router = useRouter(); + const { setUser, setTokens } = useAuthStore(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + const [acceptedTerms, setAcceptedTerms] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!acceptedTerms) { + setError('Debes aceptar los términos y condiciones para continuar.'); + return; + } + setIsLoading(true); + setError(''); + + const formData = new FormData(e.currentTarget); + + try { + const response = await register({ + empresa: { + nombre: formData.get('empresaNombre') as string, + rfc: formData.get('empresaRfc') as string, + }, + usuario: { + nombre: formData.get('nombre') as string, + email: formData.get('email') as string, + password: formData.get('password') as string, + }, + }); + setTokens(response.accessToken, response.refreshToken); + setUser(response.user); + router.push('/dashboard'); + } catch (err: any) { + setError(err.response?.data?.message || 'Error al registrarse'); + } finally { + setIsLoading(false); + } + } + + return ( +
+ + + Crear Cuenta + + Registra tu empresa y comienza tu prueba gratuita + + +
+ + {error && ( +
+ {error} +
+ )} + +
+ + + +
+ +
+ + + + +
+ + +
+ + +

+ ¿Ya tienes cuenta?{' '} + + Inicia sesión + +

+
+
+
+
+ ); +} diff --git a/apps/web/app/(auth)/reset-password/page.tsx b/apps/web/app/(auth)/reset-password/page.tsx new file mode 100644 index 0000000..b3b783c --- /dev/null +++ b/apps/web/app/(auth)/reset-password/page.tsx @@ -0,0 +1,166 @@ +'use client'; + +import { Suspense, useState } from 'react'; +import Link from 'next/link'; +import Image from 'next/image'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { Button, Input, Label, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@horux/shared-ui'; +import { confirmPasswordReset } from '@/lib/api/auth'; +import { KeyRound, CheckCircle2, AlertTriangle } from 'lucide-react'; + +function ResetPasswordContent() { + const router = useRouter(); + const searchParams = useSearchParams(); + const token = searchParams.get('token') || ''; + + const [isLoading, setIsLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + + if (!token) { + return ( + + +
+
+ +
+
+ Enlace inválido + + El enlace no incluye un token de recuperación válido. Solicita uno nuevo desde la pantalla de login. + +
+ + + + + +
+ ); + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(''); + + if (newPassword.length < 8) { + setError('La contraseña debe tener al menos 8 caracteres'); + return; + } + if (newPassword !== confirmPassword) { + setError('Las contraseñas no coinciden'); + return; + } + + setIsLoading(true); + try { + await confirmPasswordReset(token, newPassword); + setSuccess(true); + setTimeout(() => router.push('/login'), 3000); + } catch (err: any) { + setError(err.response?.data?.message || 'Error al actualizar contraseña'); + } finally { + setIsLoading(false); + } + } + + if (success) { + return ( + + +
+
+ +
+
+ Contraseña actualizada + + Tu contraseña fue cambiada exitosamente. Redireccionando al login... + +
+ + + + + +
+ ); + } + + return ( + + +
+ Horux360 +
+ + + Nueva contraseña + + + Ingresa tu nueva contraseña. Mínimo 8 caracteres. + +
+
+ + {error && ( +
+ {error} +
+ )} +
+ + setNewPassword(e.target.value)} + placeholder="••••••••" + required + minLength={8} + autoFocus + /> +
+
+ + setConfirmPassword(e.target.value)} + placeholder="••••••••" + required + minLength={8} + /> +
+

+ Al actualizar, se cerrarán todas tus sesiones activas por seguridad. Tendrás que volver a iniciar sesión. +

+
+ + + + Volver al login + + +
+
+ ); +} + +export default function ResetPasswordPage() { + return ( +
+
+ Cargando...}> + + +
+
+ ); +} diff --git a/apps/web/app/(dashboard)/admin/audit-log/page.tsx b/apps/web/app/(dashboard)/admin/audit-log/page.tsx new file mode 100644 index 0000000..43911a3 --- /dev/null +++ b/apps/web/app/(dashboard)/admin/audit-log/page.tsx @@ -0,0 +1,229 @@ +'use client'; + +import { useState } from 'react'; +import { Header } from '@/components/layouts/header'; +import { Card, CardContent, CardHeader, CardTitle, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@horux/shared-ui'; +import { useAuthStore } from '@/stores/auth-store'; +import { isGlobalAdminRfc } from '@horux/shared'; +import { useAuditLog } from '@/lib/hooks/use-audit-log'; +import { ChevronLeft, ChevronRight, Search, X, FileWarning, ShieldAlert } from 'lucide-react'; + +const ACTION_GROUPS = [ + { value: '', label: 'Todas las acciones' }, + { value: 'user.', label: 'Usuarios (login, logout, password)' }, + { value: 'subscription.', label: 'Suscripciones (crear, cancelar, cambiar, reactivar)' }, + { value: 'trial.', label: 'Trials' }, + { value: 'price.', label: 'Precios' }, + { value: 'invoice.', label: 'Facturación' }, + { value: 'payment.', label: 'Pagos' }, + { value: 'tenant.', label: 'Tenants' }, + { value: 'fiel.', label: 'FIEL' }, +]; + +const ACTION_LABELS: Record = { + 'user.login': { label: 'Login', color: 'bg-blue-50 text-blue-700' }, + 'user.logout': { label: 'Logout', color: 'bg-slate-50 text-slate-700' }, + 'user.password_changed': { label: 'Cambio password', color: 'bg-amber-50 text-amber-700' }, + 'trial.started': { label: 'Trial iniciado', color: 'bg-sky-50 text-sky-700' }, + 'subscription.created': { label: 'Suscripción creada',color: 'bg-green-50 text-green-700' }, + 'subscription.cancelled': { label: 'Suscripción cancelada', color: 'bg-orange-50 text-orange-700' }, + 'subscription.reactivated': { label: 'Reactivada', color: 'bg-teal-50 text-teal-700' }, + 'subscription.plan_changed': { label: 'Cambio de plan', color: 'bg-indigo-50 text-indigo-700' }, + 'price.updated': { label: 'Precio editado', color: 'bg-purple-50 text-purple-700' }, + 'invoice.emitted_auto': { label: 'Factura auto', color: 'bg-emerald-50 text-emerald-700' }, + 'invoice.emitted_manual': { label: 'Factura manual', color: 'bg-emerald-50 text-emerald-700' }, + 'payment.marked_paid_manually': { label: 'Pago marcado manual', color: 'bg-lime-50 text-lime-700' }, +}; + +function formatDateTime(iso: string): string { + const d = new Date(iso); + return d.toLocaleString('es-MX', { dateStyle: 'short', timeStyle: 'medium' }); +} + +function ActionBadge({ action }: { action: string }) { + const cfg = ACTION_LABELS[action] || { label: action, color: 'bg-muted text-muted-foreground' }; + return {cfg.label}; +} + +export default function AuditLogPage() { + const { user } = useAuthStore(); + const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles); + + const [filters, setFilters] = useState({ + action: '', + tenantId: '', + userId: '', + from: '', + to: '', + page: 1, + limit: 50, + }); + const [expandedId, setExpandedId] = useState(null); + + const { data, isLoading } = useAuditLog(filters); + + if (!isGlobalAdmin) { + return ( + <> +
+
+ + + +

Acceso restringido

+

+ Solo el administrador global puede consultar el audit log. +

+
+
+
+ + ); + } + + const clearFilters = () => setFilters({ action: '', tenantId: '', userId: '', from: '', to: '', page: 1, limit: 50 }); + + return ( + <> +
+
+ + + + + Filtros + + + +
+
+ + +
+
+ + setFilters(f => ({ ...f, tenantId: e.target.value, page: 1 }))} placeholder="UUID del tenant" /> +
+
+ + setFilters(f => ({ ...f, userId: e.target.value, page: 1 }))} placeholder="UUID del usuario" /> +
+
+ + setFilters(f => ({ ...f, from: e.target.value, page: 1 }))} /> +
+
+ + setFilters(f => ({ ...f, to: e.target.value, page: 1 }))} /> +
+
+
+ +
+
+
+ + + + + Eventos {data?.total !== undefined && ({data.total.toLocaleString('es-MX')} totales)} + + + + {isLoading ? ( +

Cargando...

+ ) : !data || data.data.length === 0 ? ( +

No hay eventos con estos filtros.

+ ) : ( + <> +
+ + + + + + + + + + + + + {data.data.map(row => ( + + + + + + + + + ))} + +
FechaAcciónUsuarioTenantEntidad
{formatDateTime(row.createdAt)} + {row.user ? ( +
+
{row.user.nombre}
+
{row.user.email}
+
+ ) : Sistema} +
+ {row.tenant ? ( +
+
{row.tenant.nombre}
+
{row.tenant.rfc}
+
+ ) : } +
+ {row.entityType ? `${row.entityType}${row.entityId ? ` ${row.entityId.slice(0, 8)}` : ''}` : '—'} + + +
+
+ + {expandedId && (() => { + const row = data.data.find(r => r.id === expandedId); + if (!row) return null; + return ( + + +

Metadata del evento {row.id}

+
{JSON.stringify(row.metadata, null, 2)}
+
+
+ ); + })()} + + {data.totalPages > 1 && ( +
+

Página {data.page} de {data.totalPages}

+
+ + +
+
+ )} + + )} +
+
+
+ + ); +} diff --git a/apps/web/app/(dashboard)/admin/staff/page.tsx b/apps/web/app/(dashboard)/admin/staff/page.tsx new file mode 100644 index 0000000..3e5e3f1 --- /dev/null +++ b/apps/web/app/(dashboard)/admin/staff/page.tsx @@ -0,0 +1,264 @@ +'use client'; + +import { useState } from 'react'; +import { Header } from '@/components/layouts/header'; +import { Card, CardContent, CardHeader, CardTitle, Button, Input, Label, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@horux/shared-ui'; +import { useAuthStore } from '@/stores/auth-store'; +import { isGlobalAdminRfc, type PlatformRole } from '@horux/shared'; +import { useStaff, useSearchUsers, useGrantRole, useRevokeRole } from '@/lib/hooks/use-platform-staff'; +import { ShieldAlert, Shield, ShieldCheck, HeadphonesIcon, TrendingUp, DollarSign, UserPlus, X, Loader2, Search, Cpu } from 'lucide-react'; + +const ROLE_META: Record = { + platform_admin: { label: 'Admin', desc: 'Todo: gestión staff, precios, clientes, facturas', icon: ShieldCheck, color: 'bg-red-100 text-red-700 border-red-200' }, + platform_ti: { label: 'TI', desc: 'Equipo de TI. Mismos permisos que Admin (diferencia solo en trazabilidad)', icon: Cpu, color: 'bg-slate-100 text-slate-700 border-slate-200' }, + platform_support: { label: 'Support', desc: 'Ver tenants, resolver tickets', icon: HeadphonesIcon, color: 'bg-blue-100 text-blue-700 border-blue-200' }, + platform_sales: { label: 'Sales', desc: 'Crear/editar clientes, ver suscripciones', icon: TrendingUp, color: 'bg-green-100 text-green-700 border-green-200' }, + platform_finance: { label: 'Finance', desc: 'Pagos, facturas manuales, editar precios', icon: DollarSign, color: 'bg-amber-100 text-amber-700 border-amber-200' }, +}; + +const ALL_ROLES: PlatformRole[] = ['platform_admin', 'platform_ti', 'platform_support', 'platform_sales', 'platform_finance']; + +export default function StaffPage() { + const { user } = useAuthStore(); + const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles); + + const { data: staff = [], isLoading } = useStaff(); + const grantRole = useGrantRole(); + const revokeRole = useRevokeRole(); + + const [addOpen, setAddOpen] = useState(false); + const [searchQ, setSearchQ] = useState(''); + const { data: candidates = [] } = useSearchUsers(searchQ); + const [pickedUserId, setPickedUserId] = useState(null); + const [pickedRole, setPickedRole] = useState('platform_support'); + + if (!isGlobalAdmin) { + return ( + <> +
+
+ + + +

Acceso restringido

+

Solo platform_admin puede gestionar staff.

+
+
+
+ + ); + } + + const handleGrant = async () => { + if (!pickedUserId) return; + try { + await grantRole.mutateAsync({ userId: pickedUserId, role: pickedRole }); + setAddOpen(false); + setSearchQ(''); + setPickedUserId(null); + } catch (err: any) { + alert(err?.response?.data?.message || err?.message || 'Error al asignar rol'); + } + }; + + const handleRevoke = async (userId: string, role: PlatformRole, userEmail: string) => { + if (!confirm(`¿Quitar el rol "${ROLE_META[role].label}" a ${userEmail}?`)) return; + try { + await revokeRole.mutateAsync({ userId, role }); + } catch (err: any) { + alert(err?.response?.data?.message || err?.message || 'Error al quitar rol'); + } + }; + + return ( + <> +
+
+
+
+

+ Staff interno de Horux 360 con poderes transversales. platform_admin implica todos los otros roles. +

+
+ +
+ + + + Equipo ({staff.length}) + + + {isLoading ? ( +

Cargando...

+ ) : staff.length === 0 ? ( +

+ Todavía no hay staff. Agrega al primer miembro con el botón arriba. +

+ ) : ( +
+ + + + + + + + + + + {staff.map(s => ( + + + + + + + ))} + +
UsuarioTenant origenRolesAcciones
+
{s.nombre}
+
{s.email}
+
+ {s.tenant ? ( +
+
{s.tenant.nombre}
+
{s.tenant.rfc}
+
+ ) : } +
+
+ {s.roles.map(r => { + const meta = ROLE_META[r]; + const Icon = meta.icon; + return ( + + + {meta.label} + + + ); + })} +
+
+ +
+
+ )} +
+
+ + + + + + Descripción de roles + + + +
+ {ALL_ROLES.map(r => { + const m = ROLE_META[r]; + const Icon = m.icon; + return ( +
+
+ + {m.label} +
+

{m.desc}

+
+ ); + })} +
+
+
+ + {/* Add staff dialog */} + + + + Agregar rol de staff + + Busca al usuario por email o nombre y asígnale un rol de plataforma. + + + +
+
+ +
+ + { setSearchQ(e.target.value); setPickedUserId(null); }} + placeholder="email o nombre (min 2 caracteres)" + className="pl-8" + /> +
+ {searchQ.length >= 2 && candidates.length > 0 && !pickedUserId && ( +
+ {candidates.map(c => ( + + ))} +
+ )} +
+ +
+ +
+ {ALL_ROLES.map(r => { + const m = ROLE_META[r]; + const Icon = m.icon; + return ( + + ); + })} +
+
+
+ + + + + +
+
+
+ + ); +} diff --git a/apps/web/app/(dashboard)/admin/usuarios/page.tsx b/apps/web/app/(dashboard)/admin/usuarios/page.tsx new file mode 100644 index 0000000..ddcba35 --- /dev/null +++ b/apps/web/app/(dashboard)/admin/usuarios/page.tsx @@ -0,0 +1,311 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@horux/shared-ui'; +import { useAllUsuarios, useUpdateUsuarioGlobal, useDeleteUsuarioGlobal } from '@/lib/hooks/use-usuarios'; +import { getTenants, type Tenant } from '@/lib/api/tenants'; +import { useAuthStore } from '@/stores/auth-store'; +import { Users, Pencil, Trash2, Shield, Eye, Calculator, Building2, X, Check, UserCog, UserCheck, User, Briefcase } from 'lucide-react'; +import { cn } from '@horux/shared-ui'; + +// Mapa de roles + fallback defensivo. El fork despacho introduce roles +// adicionales (cfo, supervisor, auxiliar, cliente) que no estaban en +// Horux 360 single-tenant; si llega un rol no mapeado (ej. uno nuevo +// agregado en BD sin tocar este archivo), `defaultRoleInfo` previene +// runtime errors al hacer `roleInfo.icon`. +const roleLabels: Record = { + owner: { label: 'Dueño', icon: Shield, color: 'text-primary' }, + cfo: { label: 'CFO', icon: Briefcase, color: 'text-indigo-600' }, + contador: { label: 'Contador', icon: Calculator, color: 'text-green-600' }, + supervisor: { label: 'Supervisor', icon: UserCheck, color: 'text-blue-600' }, + auxiliar: { label: 'Auxiliar', icon: UserCog, color: 'text-cyan-600' }, + cliente: { label: 'Cliente', icon: User, color: 'text-amber-600' }, + visor: { label: 'Visor', icon: Eye, color: 'text-muted-foreground' }, +}; +const defaultRoleInfo = { label: 'Sin rol', icon: User, color: 'text-muted-foreground' }; + +interface EditingUser { + id: string; + nombre: string; + role: 'owner' | 'contador' | 'visor'; + tenantId: string; +} + +export default function AdminUsuariosPage() { + const { user: currentUser } = useAuthStore(); + const { data: usuarios, isLoading, error } = useAllUsuarios(); + const updateUsuario = useUpdateUsuarioGlobal(); + const deleteUsuario = useDeleteUsuarioGlobal(); + + const [tenants, setTenants] = useState([]); + const [editingUser, setEditingUser] = useState(null); + const [filterTenant, setFilterTenant] = useState('all'); + const [searchTerm, setSearchTerm] = useState(''); + + useEffect(() => { + getTenants().then(setTenants).catch(console.error); + }, []); + + const handleEdit = (usuario: any) => { + setEditingUser({ + id: usuario.id, + nombre: usuario.nombre, + role: usuario.role, + tenantId: usuario.tenantId, + }); + }; + + const handleSave = async () => { + if (!editingUser) return; + try { + await updateUsuario.mutateAsync({ + id: editingUser.id, + data: { + nombre: editingUser.nombre, + role: editingUser.role, + tenantId: editingUser.tenantId, + }, + }); + setEditingUser(null); + } catch (err: any) { + alert(err.response?.data?.error || 'Error al actualizar usuario'); + } + }; + + const handleDelete = async (id: string) => { + if (!confirm('Estas seguro de eliminar este usuario?')) return; + try { + await deleteUsuario.mutateAsync(id); + } catch (err: any) { + alert(err.response?.data?.error || 'Error al eliminar usuario'); + } + }; + + const filteredUsuarios = usuarios?.filter(u => { + const matchesTenant = filterTenant === 'all' || u.tenantId === filterTenant; + const matchesSearch = !searchTerm || + u.nombre.toLowerCase().includes(searchTerm.toLowerCase()) || + u.email.toLowerCase().includes(searchTerm.toLowerCase()); + return matchesTenant && matchesSearch; + }); + + // Agrupar por empresa + const groupedByTenant = filteredUsuarios?.reduce((acc, u) => { + const key = u.tenantId || 'sin-empresa'; + if (!acc[key]) { + acc[key] = { + tenantName: u.tenantName || 'Sin empresa', + users: [], + }; + } + acc[key].users.push(u); + return acc; + }, {} as Record); + + if (error) { + return ( + + + +

+ No tienes permisos para ver esta pagina o ocurrio un error. +

+
+
+
+ ); + } + + return ( + +
+ {/* Filtros */} + + +
+
+ setSearchTerm(e.target.value)} + /> +
+
+ +
+
+
+
+ + {/* Stats */} +
+
+ + {filteredUsuarios?.length || 0} usuarios +
+
+ + {Object.keys(groupedByTenant || {}).length} empresas +
+
+ + {/* Users by tenant */} + {isLoading ? ( + + + Cargando usuarios... + + + ) : ( + Object.entries(groupedByTenant || {}).map(([tenantId, { tenantName, users }]) => ( + + + + + {tenantName} + + ({users?.length} usuarios) + + + + +
+ {users?.map(usuario => { + const roleInfo = roleLabels[usuario.role] || defaultRoleInfo; + const RoleIcon = roleInfo.icon; + const isCurrentUser = usuario.id === currentUser?.id; + const isEditing = editingUser?.id === usuario.id; + + return ( +
+
+
+ {usuario.nombre.charAt(0).toUpperCase()} +
+
+ {isEditing ? ( +
+ setEditingUser({ ...editingUser, nombre: e.target.value })} + className="h-8" + /> +
+ + +
+
+ ) : ( + <> +
+ {usuario.nombre} + {isCurrentUser && ( + Tu + )} + {!usuario.active && ( + Inactivo + )} +
+
{usuario.email}
+ + )} +
+
+
+ {!isEditing && ( +
+ + {roleInfo.label} +
+ )} + {!isCurrentUser && ( +
+ {isEditing ? ( + <> + + + + ) : ( + <> + + + + )} +
+ )} +
+
+ ); + })} +
+
+
+ )) + )} +
+
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/cancelaciones-periodo-anterior/page.tsx b/apps/web/app/(dashboard)/alertas/cancelaciones-periodo-anterior/page.tsx new file mode 100644 index 0000000..41200ca --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/cancelaciones-periodo-anterior/page.tsx @@ -0,0 +1,126 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, Button, SortableHeader } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; +import { exportToExcel } from '@/lib/export-excel'; +import { useTableSort } from '@horux/shared-ui'; +import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; +import { Eye, Download } from 'lucide-react'; +import type { Cfdi } from '@horux/shared'; +import { useContribuyenteStore } from '@/stores/contribuyente-store'; + +const EXCEL_COLUMNS = [ + { header: 'UUID', key: 'uuid', width: 40 }, + { header: 'Fecha Emision', key: '_fechaEmision', width: 15 }, + { header: 'Fecha Cancelacion', key: '_fechaCancelacion', width: 18 }, + { header: 'RFC Emisor', key: 'rfcEmisor', width: 15 }, + { header: 'Nombre Emisor', key: 'nombreEmisor', width: 30 }, + { header: 'RFC Receptor', key: 'rfcReceptor', width: 15 }, + { header: 'Nombre Receptor', key: 'nombreReceptor', width: 30 }, + { header: 'Total MXN', key: '_totalMxn', width: 15 }, +]; + +function prepareRows(data: any[]) { + return data.map((c) => ({ + ...c, + _fechaEmision: new Date(c.fechaEmision).toLocaleDateString('es-MX'), + _fechaCancelacion: c.fechaCancelacion ? new Date(c.fechaCancelacion).toLocaleDateString('es-MX') : '', + _totalMxn: Number(c.totalMxn || 0), + })); +} + +export default function CancelacionesPeriodoAnteriorPage() { + const [selectedCfdi, setSelectedCfdi] = useState(null); + const { selectedContribuyenteId } = useContribuyenteStore(); + + const { data, isLoading } = useQuery({ + queryKey: ['drilldown-cancelaciones-periodo-anterior', selectedContribuyenteId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedContribuyenteId) params.set('contribuyenteId', selectedContribuyenteId); + const res = await apiClient.get(`/alertas/drilldown/cancelaciones-periodo-anterior?${params}`); + return res.data; + }, + }); + + const { sortedData, toggleSort, getSortIndicator } = useTableSort( + data, + { + fecha: (c) => new Date(c.fechaEmision).getTime(), + cancelacion: (c: any) => c.fechaCancelacion ? new Date(c.fechaCancelacion).getTime() : 0, + total: (c) => Number(c.totalMxn || 0), + }, + 'cancelacion', + ); + + const handleExport = () => { + if (!sortedData || sortedData.length === 0) return; + exportToExcel(prepareRows(sortedData), EXCEL_COLUMNS, 'cancelaciones-periodo-anterior'); + }; + + return ( + + + + {isLoading ? ( +
Cargando...
+ ) : !data || data.length === 0 ? ( +
No hay CFDIs cancelados de periodos anteriores
+ ) : ( +
+
+

+ {data.length} CFDIs emitidos en meses anteriores y cancelados este mes +

+ +
+ + + + + toggleSort('fecha')} /> + toggleSort('cancelacion')} /> + + + toggleSort('total')} /> + + + + + {(sortedData || []).map((cfdi: any) => ( + + + + + + + + + + ))} + +
UUIDRFC EmisorRFC Receptor
{cfdi.uuid?.substring(0, 8)}{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}{cfdi.fechaCancelacion ? new Date(cfdi.fechaCancelacion).toLocaleDateString('es-MX') : '-'}{cfdi.rfcEmisor}{cfdi.rfcReceptor}{formatCurrency(Number(cfdi.totalMxn))} + +
+
+ )} +
+
+ + setSelectedCfdi(null)} + /> +
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/cancelaciones/page.tsx b/apps/web/app/(dashboard)/alertas/cancelaciones/page.tsx new file mode 100644 index 0000000..49afd7c --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/cancelaciones/page.tsx @@ -0,0 +1,120 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, Button, SortableHeader } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; +import { exportToExcel } from '@/lib/export-excel'; +import { useTableSort } from '@horux/shared-ui'; +import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; +import { Eye, Download } from 'lucide-react'; +import type { Cfdi } from '@horux/shared'; + +const EXCEL_COLUMNS = [ + { header: 'UUID', key: 'uuid', width: 40 }, + { header: 'Fecha Emision', key: '_fechaEmision', width: 15 }, + { header: 'Fecha Cancelacion', key: '_fechaCancelacion', width: 18 }, + { header: 'RFC Emisor', key: 'rfcEmisor', width: 15 }, + { header: 'Nombre Emisor', key: 'nombreEmisor', width: 30 }, + { header: 'RFC Receptor', key: 'rfcReceptor', width: 15 }, + { header: 'Nombre Receptor', key: 'nombreReceptor', width: 30 }, + { header: 'Total MXN', key: '_totalMxn', width: 15 }, +]; + +function prepareRows(data: any[]) { + return data.map((c) => ({ + ...c, + _fechaEmision: new Date(c.fechaEmision).toLocaleDateString('es-MX'), + _fechaCancelacion: c.fechaCancelacion ? new Date(c.fechaCancelacion).toLocaleDateString('es-MX') : '', + _totalMxn: Number(c.totalMxn || 0), + })); +} + +export default function CancelacionesPage() { + const [selectedCfdi, setSelectedCfdi] = useState(null); + + const { data, isLoading } = useQuery({ + queryKey: ['drilldown-cancelaciones'], + queryFn: async () => { + const res = await apiClient.get('/alertas/drilldown/cancelaciones'); + return res.data; + }, + }); + + const { sortedData, toggleSort, getSortIndicator } = useTableSort( + data, + { + fecha: (c) => new Date(c.fechaEmision).getTime(), + cancelacion: (c: any) => c.fechaCancelacion ? new Date(c.fechaCancelacion).getTime() : 0, + total: (c) => Number(c.totalMxn || 0), + }, + 'cancelacion', + ); + + const handleExport = () => { + if (!sortedData || sortedData.length === 0) return; + exportToExcel(prepareRows(sortedData), EXCEL_COLUMNS, 'cfdis-cancelados'); + }; + + return ( + + + + {isLoading ? ( +
Cargando...
+ ) : !data || data.length === 0 ? ( +
No hay CFDIs cancelados
+ ) : ( +
+
+

{data.length} CFDIs cancelados

+ +
+ + + + + toggleSort('fecha')} /> + toggleSort('cancelacion')} /> + + + toggleSort('total')} /> + + + + + {(sortedData || []).map((cfdi: any) => ( + + + + + + + + + + ))} + +
UUIDRFC EmisorRFC Receptor
{cfdi.uuid?.substring(0, 8)}{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}{cfdi.fechaCancelacion ? new Date(cfdi.fechaCancelacion).toLocaleDateString('es-MX') : '-'}{cfdi.rfcEmisor}{cfdi.rfcReceptor}{formatCurrency(Number(cfdi.totalMxn))} + +
+
+ )} +
+
+ + setSelectedCfdi(null)} + /> +
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/concentracion-clientes/page.tsx b/apps/web/app/(dashboard)/alertas/concentracion-clientes/page.tsx new file mode 100644 index 0000000..339a01e --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/concentracion-clientes/page.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle, SortableHeader } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; +import { useTableSort } from '@horux/shared-ui'; + +export default function ConcentracionClientesPage() { + const { data, isLoading } = useQuery({ + queryKey: ['drilldown-concentracion-clientes'], + queryFn: async () => { + const res = await apiClient.get('/alertas/drilldown/concentracion-clientes'); + return res.data; + }, + }); + + const { sortedData, toggleSort, getSortIndicator } = useTableSort( + data, + { + cfdis: (d) => Number(d.cantidad || 0), + total: (d) => Number(d.total || 0), + }, + 'total', + ); + + return ( + + + + Participacion por Cliente (Facturas Emitidas) + + + {isLoading ? ( +
Cargando...
+ ) : !data || data.length === 0 ? ( +
No hay datos
+ ) : ( +
+ + + + + + toggleSort('cfdis')} /> + toggleSort('total')} /> + + + + + {(sortedData || []).map((d: any) => ( + + + + + + + + ))} + +
RFCNombreParticipacion
{d.rfc}{d.nombre}{d.cantidad}{formatCurrency(d.total)} +
+
+
+
+ {d.participacion}% +
+
+
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/concentracion-proveedores/page.tsx b/apps/web/app/(dashboard)/alertas/concentracion-proveedores/page.tsx new file mode 100644 index 0000000..ba96a65 --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/concentracion-proveedores/page.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle, SortableHeader } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; +import { useTableSort } from '@horux/shared-ui'; + +export default function ConcentracionProveedoresPage() { + const { data, isLoading } = useQuery({ + queryKey: ['drilldown-concentracion-proveedores'], + queryFn: async () => { + const res = await apiClient.get('/alertas/drilldown/concentracion-proveedores'); + return res.data; + }, + }); + + const { sortedData, toggleSort, getSortIndicator } = useTableSort( + data, + { + cfdis: (d) => Number(d.cantidad || 0), + total: (d) => Number(d.total || 0), + }, + 'total', + ); + + return ( + + + + Participacion por Proveedor (Facturas Recibidas) + + + {isLoading ? ( +
Cargando...
+ ) : !data || data.length === 0 ? ( +
No hay datos
+ ) : ( +
+ + + + + + toggleSort('cfdis')} /> + toggleSort('total')} /> + + + + + {(sortedData || []).map((d: any) => ( + + + + + + + + ))} + +
RFCNombreParticipacion
{d.rfc}{d.nombre}{d.cantidad}{formatCurrency(d.total)} +
+
+
+
+ {d.participacion}% +
+
+
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/discrepancia-regimen/page.tsx b/apps/web/app/(dashboard)/alertas/discrepancia-regimen/page.tsx new file mode 100644 index 0000000..61d76e5 --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/discrepancia-regimen/page.tsx @@ -0,0 +1,344 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle, Button, SortableHeader, Input } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; +import { exportToExcel } from '@/lib/export-excel'; +import { useTableSort } from '@horux/shared-ui'; +import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; +import { Eye, Download, CheckSquare, Square, EyeOff, Filter, RotateCcw } from 'lucide-react'; +import type { Cfdi } from '@horux/shared'; +import { useContribuyenteStore } from '@/stores/contribuyente-store'; + +const TIPO_ALERTA = 'discrepancia-regimen'; + +const EXCEL_COLUMNS = [ + { header: 'UUID', key: 'uuid', width: 40 }, + { header: 'Fecha', key: '_fecha', width: 15 }, + { header: 'RFC Emisor', key: 'rfcEmisor', width: 15 }, + { header: 'Nombre Emisor', key: 'nombreEmisor', width: 30 }, + { header: 'RFC Receptor', key: 'rfcReceptor', width: 15 }, + { header: 'Nombre Receptor', key: 'nombreReceptor', width: 30 }, + { header: 'Regimen Receptor', key: 'regimenReceptor', width: 18 }, + { header: 'Total MXN', key: '_totalMxn', width: 15 }, +]; + +function prepareRows(data: any[]) { + return data.map((c) => ({ + ...c, + _fecha: new Date(c.fechaEmision).toLocaleDateString('es-MX'), + _totalMxn: Number(c.totalMxn || 0), + regimenReceptor: c.regimenReceptor || c.regimenFiscalReceptor || '', + })); +} + +export default function DiscrepanciaRegimenPage() { + const [selectedCfdi, setSelectedCfdi] = useState(null); + const [checked, setChecked] = useState>(new Set()); + const [view, setView] = useState<'activos' | 'descartados'>('activos'); + const { selectedContribuyenteId } = useContribuyenteStore(); + const queryClient = useQueryClient(); + + // Filters + const [fechaDesde, setFechaDesde] = useState(''); + const [fechaHasta, setFechaHasta] = useState(''); + const [regimenFilter, setRegimenFilter] = useState(''); + + // Activos (lo que aparece en la alerta) + const activosQ = useQuery({ + queryKey: ['drilldown-discrepancia', selectedContribuyenteId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedContribuyenteId) params.set('contribuyenteId', selectedContribuyenteId); + const res = await apiClient.get(`/alertas/drilldown/discrepancia-regimen?${params}`); + return res.data; + }, + enabled: view === 'activos', + }); + + // Descartados (lo que ya se marcó para ignorar) + const descartadosQ = useQuery({ + queryKey: ['descartados-discrepancia', selectedContribuyenteId], + queryFn: async () => { + const params = new URLSearchParams({ tipoAlerta: TIPO_ALERTA }); + if (selectedContribuyenteId) params.set('contribuyenteId', selectedContribuyenteId); + const res = await apiClient.get<{ data: Cfdi[] }>(`/alertas/descartados?${params}`); + return res.data.data; + }, + enabled: view === 'descartados', + }); + + const data = view === 'activos' ? activosQ.data : descartadosQ.data; + const isLoading = view === 'activos' ? activosQ.isLoading : descartadosQ.isLoading; + + // Extract unique regímenes for the filter dropdown + const regimenesUnicos = useMemo(() => { + if (!data) return []; + const set = new Set(); + data.forEach((c: any) => { + const reg = c.regimenReceptor || c.regimenFiscalReceptor; + if (reg) set.add(reg); + }); + return [...set].sort(); + }, [data]); + + // Apply filters: fecha + regimen (descartados already excluded by backend) + const visibleData = useMemo(() => { + if (!data) return []; + let filtered = data; + + if (fechaDesde) { + filtered = filtered.filter(c => c.fechaEmision >= fechaDesde); + } + if (fechaHasta) { + filtered = filtered.filter(c => c.fechaEmision <= fechaHasta + 'T23:59:59'); + } + if (regimenFilter) { + filtered = filtered.filter((c: any) => (c.regimenReceptor || c.regimenFiscalReceptor) === regimenFilter); + } + + return filtered; + }, [data, fechaDesde, fechaHasta, regimenFilter]); + + const { sortedData, toggleSort, getSortIndicator } = useTableSort( + visibleData, + { + fecha: (c) => new Date(c.fechaEmision).getTime(), + total: (c) => Number(c.totalMxn || 0), + }, + 'fecha', + ); + + const handleExport = () => { + if (!sortedData || sortedData.length === 0) return; + exportToExcel(prepareRows(sortedData), EXCEL_COLUMNS, 'cfdis-discrepancia-regimen'); + }; + + const toggleCheck = (id: string) => { + setChecked(prev => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); else next.add(id); + return next; + }); + }; + + const toggleSelectAll = () => { + if (!sortedData) return; + if (checked.size === sortedData.length) { + setChecked(new Set()); + } else { + setChecked(new Set(sortedData.map(c => String(c.id)))); + } + }; + + const invalidateAll = () => { + queryClient.invalidateQueries({ queryKey: ['drilldown-discrepancia'] }); + queryClient.invalidateQueries({ queryKey: ['descartados-discrepancia'] }); + queryClient.invalidateQueries({ queryKey: ['alertas-automaticas'] }); + queryClient.invalidateQueries({ queryKey: ['alertas'] }); + }; + + const handleDescartar = async () => { + const cfdiIds = [...checked].map(id => Number(id)); + try { + await apiClient.post('/alertas/descartar', { cfdiIds, tipoAlerta: TIPO_ALERTA }); + setChecked(new Set()); + invalidateAll(); + } catch { + alert('Error al descartar'); + } + }; + + const handleRestaurar = async () => { + const cfdiIds = [...checked].map(id => Number(id)); + try { + await apiClient.delete('/alertas/descartar', { data: { cfdiIds, tipoAlerta: TIPO_ALERTA } }); + setChecked(new Set()); + invalidateAll(); + } catch { + alert('Error al restaurar'); + } + }; + + const handleChangeView = (next: 'activos' | 'descartados') => { + setView(next); + setChecked(new Set()); + }; + + const handleClearFilters = () => { + setFechaDesde(''); + setFechaHasta(''); + setRegimenFilter(''); + }; + + const hasActiveFilters = fechaDesde || fechaHasta || regimenFilter; + + const allChecked = sortedData && sortedData.length > 0 && + checked.size === sortedData.length; + + return ( + + + +
+ + {view === 'activos' + ? 'Facturas recibidas con régimen fiscal que no coincide con los regímenes activos' + : 'CFDIs descartados manualmente — ignorados en la alerta'} + +
+ {/* Toggle Activos / Descartados */} +
+ + +
+ {checked.size > 0 && view === 'activos' && ( + + )} + {checked.size > 0 && view === 'descartados' && ( + + )} + {data && data.length > 0 && ( + + )} +
+
+ + {/* Filters */} +
+
+ + Filtros: +
+
+ + setFechaDesde(e.target.value)} + className="h-8 w-[150px] text-sm" + /> +
+
+ + setFechaHasta(e.target.value)} + className="h-8 w-[150px] text-sm" + /> +
+
+ + +
+ {hasActiveFilters && ( + + )} +
+
+ + {isLoading ? ( +
Cargando...
+ ) : !sortedData || sortedData.length === 0 ? ( +
+ {hasActiveFilters + ? 'No hay resultados con los filtros seleccionados' + : view === 'activos' + ? 'No hay discrepancias nuevas' + : 'No hay CFDIs descartados'} +
+ ) : ( +
+ + + + + + toggleSort('fecha')} /> + + + + toggleSort('total')} /> + + + + + {sortedData.map((cfdi: any) => ( + + + + + + + + + + + ))} + +
+ + UUIDRFC EmisorNombre EmisorRégimen Receptor
+ + {cfdi.uuid?.substring(0, 8)}{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}{cfdi.rfcEmisor}{cfdi.nombreEmisor}{cfdi.regimenReceptor}{formatCurrency(Number(cfdi.totalMxn))} + +
+

+ {sortedData.length} CFDI{sortedData.length !== 1 ? 's' : ''} {view === 'activos' ? 'con discrepancia' : 'descartados'} + {hasActiveFilters && data && ` (de ${data.length} total)`} +

+
+ )} +
+
+ + setSelectedCfdi(null)} + /> +
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/efectivo/page.tsx b/apps/web/app/(dashboard)/alertas/efectivo/page.tsx new file mode 100644 index 0000000..14fea33 --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/efectivo/page.tsx @@ -0,0 +1,118 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, Button, SortableHeader } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; +import { exportToExcel } from '@/lib/export-excel'; +import { useTableSort } from '@horux/shared-ui'; +import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; +import { Eye, Download } from 'lucide-react'; +import type { Cfdi } from '@horux/shared'; + +const EXCEL_COLUMNS = [ + { header: 'UUID', key: 'uuid', width: 40 }, + { header: 'Fecha', key: '_fecha', width: 15 }, + { header: 'RFC Emisor', key: 'rfcEmisor', width: 15 }, + { header: 'Nombre Emisor', key: 'nombreEmisor', width: 30 }, + { header: 'RFC Receptor', key: 'rfcReceptor', width: 15 }, + { header: 'Nombre Receptor', key: 'nombreReceptor', width: 30 }, + { header: 'Total MXN', key: '_totalMxn', width: 15 }, + { header: 'Forma Pago', key: 'formaPago', width: 12 }, +]; + +function prepareRows(data: any[]) { + return data.map((c) => ({ + ...c, + _fecha: new Date(c.fechaEmision).toLocaleDateString('es-MX'), + _totalMxn: Number(c.totalMxn || 0), + })); +} + +export default function EfectivoPage() { + const [selectedCfdi, setSelectedCfdi] = useState(null); + + const { data, isLoading } = useQuery({ + queryKey: ['drilldown-efectivo'], + queryFn: async () => { + const res = await apiClient.get('/alertas/drilldown/efectivo'); + return res.data; + }, + }); + + const { sortedData, toggleSort, getSortIndicator } = useTableSort( + data, + { + fecha: (c) => new Date(c.fechaEmision).getTime(), + total: (c) => Number(c.totalMxn || 0), + }, + 'fecha', + ); + + const handleExport = () => { + if (!sortedData || sortedData.length === 0) return; + exportToExcel(prepareRows(sortedData), EXCEL_COLUMNS, 'cfdis-pago-efectivo'); + }; + + return ( + + + + {isLoading ? ( +
Cargando...
+ ) : !data || data.length === 0 ? ( +
No hay CFDIs con pago en efectivo
+ ) : ( +
+
+

{data.length} CFDIs con pago en efectivo

+ +
+ + + + + toggleSort('fecha')} /> + + + + toggleSort('total')} /> + + + + + {(sortedData || []).map((cfdi: any) => ( + + + + + + + + + + ))} + +
UUIDRFC EmisorNombre EmisorRFC Receptor
{cfdi.uuid?.substring(0, 8)}{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}{cfdi.rfcEmisor}{cfdi.nombreEmisor}{cfdi.rfcReceptor}{formatCurrency(Number(cfdi.totalMxn))} + +
+
+ )} +
+
+ + setSelectedCfdi(null)} + /> +
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/lista-negra-clientes/page.tsx b/apps/web/app/(dashboard)/alertas/lista-negra-clientes/page.tsx new file mode 100644 index 0000000..40e7b3a --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/lista-negra-clientes/page.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; + +export default function ListaNegraClientesPage() { + const { data, isLoading } = useQuery({ + queryKey: ['drilldown-lista-negra-clientes'], + queryFn: async () => { + const res = await apiClient.get('/alertas/drilldown/lista-negra-clientes'); + return res.data; + }, + }); + + return ( + + + + Clientes a los que has facturado que aparecen en la lista del Art. 69-B + + + {isLoading ? ( +
Cargando...
+ ) : !data || data.length === 0 ? ( +
Ningun cliente en lista negra
+ ) : ( +
+ + + + + + + + + + + + {data.map((d: any) => ( + + + + + + + + ))} + +
RFCNombreSituacion SATCFDIsTotal Facturado
{d.rfc}{d.nombre} + {d.situacionSat} + {d.cantidad}{formatCurrency(d.total)}
+
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/lista-negra-proveedores/page.tsx b/apps/web/app/(dashboard)/alertas/lista-negra-proveedores/page.tsx new file mode 100644 index 0000000..e1d09d6 --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/lista-negra-proveedores/page.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; + +export default function ListaNegraProveedoresPage() { + const { data, isLoading } = useQuery({ + queryKey: ['drilldown-lista-negra-proveedores'], + queryFn: async () => { + const res = await apiClient.get('/alertas/drilldown/lista-negra-proveedores'); + return res.data; + }, + }); + + return ( + + + + Proveedores de los que has recibido facturas que aparecen en la lista del Art. 69-B + + + {isLoading ? ( +
Cargando...
+ ) : !data || data.length === 0 ? ( +
Ningun proveedor en lista negra
+ ) : ( +
+ + + + + + + + + + + + {data.map((d: any) => ( + + + + + + + + ))} + +
RFCNombreSituacion SATCFDIsTotal Facturado
{d.rfc}{d.nombre} + {d.situacionSat} + {d.cantidad}{formatCurrency(d.total)}
+
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/page.tsx b/apps/web/app/(dashboard)/alertas/page.tsx new file mode 100644 index 0000000..99e52ae --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/page.tsx @@ -0,0 +1,241 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/navigation'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle, Button } from '@horux/shared-ui'; +import { useAlertas, useAlertasStats, useUpdateAlerta, useDeleteAlerta, useMarkAllAsRead } from '@/lib/hooks/use-alertas'; +import { apiClient } from '@/lib/api/client'; +import { Bell, Check, Trash2, AlertTriangle, Info, AlertCircle, CheckCircle, ShieldAlert, ChevronRight, Clock } from 'lucide-react'; +import { cn } from '@horux/shared-ui'; +import { useContribuyenteStore } from '@/stores/contribuyente-store'; + +interface AlertaAuto { + id: string; + tipo: string; + titulo: string; + mensaje: string; + prioridad: 'alta' | 'media' | 'baja'; + detalle?: string; + valor?: number; +} + +const prioridadStyles = { + alta: 'border-l-4 border-l-destructive bg-destructive/5', + media: 'border-l-4 border-l-warning bg-warning/5', + baja: 'border-l-4 border-l-muted bg-muted/5', +}; + +const prioridadIcons = { + alta: AlertCircle, + media: AlertTriangle, + baja: Info, +}; + +export default function AlertasPage() { + const [filter, setFilter] = useState<'todas' | 'pendientes' | 'resueltas'>('pendientes'); + const { data: alertas, isLoading } = useAlertas({ + resuelta: filter === 'resueltas' ? true : filter === 'pendientes' ? false : undefined, + }); + const { data: stats } = useAlertasStats(); + const updateAlerta = useUpdateAlerta(); + const deleteAlerta = useDeleteAlerta(); + const markAllAsRead = useMarkAllAsRead(); + const router = useRouter(); + const { selectedContribuyenteId } = useContribuyenteStore(); + + const queryClient = useQueryClient(); + + const { data: alertasAuto } = useQuery({ + queryKey: ['alertas-automaticas', selectedContribuyenteId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedContribuyenteId) params.set('contribuyenteId', selectedContribuyenteId); + const res = await apiClient.get(`/alertas/automaticas?${params}`); + return res.data; + }, + }); + + const { data: alertasManuales } = useQuery({ + queryKey: ['alertas-manuales', selectedContribuyenteId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedContribuyenteId) params.set('contribuyenteId', selectedContribuyenteId); + const res = await apiClient.get(`/alertas/manuales?${params}`); + return res.data; + }, + }); + + const handleResolver = async (id: string) => { + await apiClient.patch(`/alertas/manuales/${id}/resolver`); + queryClient.invalidateQueries({ queryKey: ['alertas-manuales'] }); + }; + + const handleMarkAsRead = (id: number) => { + updateAlerta.mutate({ id, data: { leida: true } }); + }; + + const handleResolve = (id: number) => { + updateAlerta.mutate({ id, data: { resuelta: true } }); + }; + + const handleDelete = (id: number) => { + if (confirm('¿Eliminar esta alerta?')) { + deleteAlerta.mutate(id); + } + }; + + return ( + +
+ {/* Stats */} +
+ + + Alertas del Sistema + + + +
{alertasAuto?.length || 0}
+
+
+ + + Obligaciones Pendientes + + + +
{alertasManuales?.length || 0}
+
+
+ + + Total Alertas + + + +
+ {(alertasAuto?.length || 0) + (alertasManuales?.length || 0)} +
+
+
+
+ + {/* Alertas Automáticas */} + {alertasAuto && alertasAuto.length > 0 && ( + + + + + Alertas del Sistema ({alertasAuto.length}) + + + + {alertasAuto.map((alerta) => { + const Icon = alerta.prioridad === 'alta' ? AlertCircle : AlertTriangle; + return ( +
+
+ +
+

{alerta.titulo}

+

{alerta.mensaje}

+
+
+ {alerta.detalle && ( +
+ +
+ )} +
+ ); + })} +
+
+ )} + + {/* Obligaciones Fiscales Pendientes */} + + + + + Obligaciones Fiscales Pendientes ({alertasManuales?.length || 0}) + + + + {!alertasManuales || alertasManuales.length === 0 ? ( +
+ +

Todas las obligaciones fiscales estan al dia

+
+ ) : ( +
+ {alertasManuales.map((alerta: any) => { + const esPago = alerta.tipo.startsWith('pago-'); + const Icon = prioridadIcons[alerta.prioridad as keyof typeof prioridadIcons] || AlertTriangle; + return ( +
+
+ +
+

{alerta.titulo}

+

{alerta.mensaje}

+
+
+
+ + Vencio: {(() => { + const d = new Date(alerta.fechaVencimiento); + return isNaN(d.getTime()) ? '' : d.toLocaleDateString('es-MX', { day: 'numeric', month: 'short', year: 'numeric' }); + })()} + + +
+
+ ); + })} +
+ )} +
+
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/alertas/tipo-relacion-sospechosa/page.tsx b/apps/web/app/(dashboard)/alertas/tipo-relacion-sospechosa/page.tsx new file mode 100644 index 0000000..183eb25 --- /dev/null +++ b/apps/web/app/(dashboard)/alertas/tipo-relacion-sospechosa/page.tsx @@ -0,0 +1,341 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle, Button, SortableHeader, Input } from '@horux/shared-ui'; +import { apiClient } from '@/lib/api/client'; +import { formatCurrency } from '@/lib/utils'; +import { exportToExcel } from '@/lib/export-excel'; +import { useTableSort } from '@horux/shared-ui'; +import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; +import { Eye, Download, CheckSquare, Square, EyeOff, Filter, RotateCcw } from 'lucide-react'; +import type { Cfdi } from '@horux/shared'; +import { useContribuyenteStore } from '@/stores/contribuyente-store'; + +const TIPO_ALERTA = 'tipo-relacion-sospechosa'; + +const EXCEL_COLUMNS = [ + { header: 'UUID', key: 'uuid', width: 40 }, + { header: 'Fecha', key: '_fecha', width: 15 }, + { header: 'RFC Emisor', key: 'rfcEmisor', width: 15 }, + { header: 'Nombre Emisor', key: 'nombreEmisor', width: 30 }, + { header: 'RFC Receptor', key: 'rfcReceptor', width: 15 }, + { header: 'Nombre Receptor', key: 'nombreReceptor', width: 30 }, + { header: 'TipoRelacion', key: 'cfdiTipoRelacion', width: 14 }, + { header: 'CFDIs Relacionados', key: 'cfdisRelacionados', width: 50 }, + { header: 'Total MXN', key: '_totalMxn', width: 15 }, +]; + +function prepareRows(data: any[]) { + return data.map((c) => ({ + ...c, + _fecha: new Date(c.fechaEmision).toLocaleDateString('es-MX'), + _totalMxn: Number(c.totalMxn || 0), + })); +} + +export default function TipoRelacionSospechosaPage() { + const [selectedCfdi, setSelectedCfdi] = useState(null); + const [checked, setChecked] = useState>(new Set()); + const [view, setView] = useState<'activos' | 'descartados'>('activos'); + const { selectedContribuyenteId } = useContribuyenteStore(); + const queryClient = useQueryClient(); + + const [fechaDesde, setFechaDesde] = useState(''); + const [fechaHasta, setFechaHasta] = useState(''); + const [tipoRelFilter, setTipoRelFilter] = useState(''); + + const activosQ = useQuery({ + queryKey: ['drilldown-tipo-relacion-sospechosa', selectedContribuyenteId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedContribuyenteId) params.set('contribuyenteId', selectedContribuyenteId); + const res = await apiClient.get(`/alertas/drilldown/${TIPO_ALERTA}?${params}`); + return res.data; + }, + enabled: view === 'activos', + }); + + const descartadosQ = useQuery({ + queryKey: ['descartados-tipo-relacion-sospechosa', selectedContribuyenteId], + queryFn: async () => { + const params = new URLSearchParams({ tipoAlerta: TIPO_ALERTA }); + if (selectedContribuyenteId) params.set('contribuyenteId', selectedContribuyenteId); + const res = await apiClient.get<{ data: Cfdi[] }>(`/alertas/descartados?${params}`); + return res.data.data; + }, + enabled: view === 'descartados', + }); + + const data = view === 'activos' ? activosQ.data : descartadosQ.data; + const isLoading = view === 'activos' ? activosQ.isLoading : descartadosQ.isLoading; + + const tiposRelUnicos = useMemo(() => { + if (!data) return []; + const set = new Set(); + data.forEach((c: any) => { + if (c.cfdiTipoRelacion) set.add(c.cfdiTipoRelacion); + }); + return [...set].sort(); + }, [data]); + + const visibleData = useMemo(() => { + if (!data) return []; + let filtered = data; + if (fechaDesde) filtered = filtered.filter(c => c.fechaEmision >= fechaDesde); + if (fechaHasta) filtered = filtered.filter(c => c.fechaEmision <= fechaHasta + 'T23:59:59'); + if (tipoRelFilter) filtered = filtered.filter((c: any) => c.cfdiTipoRelacion === tipoRelFilter); + return filtered; + }, [data, fechaDesde, fechaHasta, tipoRelFilter]); + + const { sortedData, toggleSort, getSortIndicator } = useTableSort( + visibleData, + { + fecha: (c) => new Date(c.fechaEmision).getTime(), + total: (c) => Number(c.totalMxn || 0), + }, + 'fecha', + ); + + const handleExport = () => { + if (!sortedData || sortedData.length === 0) return; + exportToExcel(prepareRows(sortedData), EXCEL_COLUMNS, 'cfdis-tipo-relacion-sospechosa'); + }; + + const toggleCheck = (id: string) => { + setChecked(prev => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); else next.add(id); + return next; + }); + }; + + const toggleSelectAll = () => { + if (!sortedData) return; + if (checked.size === sortedData.length) { + setChecked(new Set()); + } else { + setChecked(new Set(sortedData.map(c => String(c.id)))); + } + }; + + const invalidateAll = () => { + queryClient.invalidateQueries({ queryKey: ['drilldown-tipo-relacion-sospechosa'] }); + queryClient.invalidateQueries({ queryKey: ['descartados-tipo-relacion-sospechosa'] }); + queryClient.invalidateQueries({ queryKey: ['alertas-automaticas'] }); + queryClient.invalidateQueries({ queryKey: ['alertas'] }); + }; + + const handleDescartar = async () => { + const cfdiIds = [...checked].map(id => Number(id)); + try { + await apiClient.post('/alertas/descartar', { cfdiIds, tipoAlerta: TIPO_ALERTA }); + setChecked(new Set()); + invalidateAll(); + } catch { + alert('Error al descartar'); + } + }; + + const handleRestaurar = async () => { + const cfdiIds = [...checked].map(id => Number(id)); + try { + await apiClient.delete('/alertas/descartar', { data: { cfdiIds, tipoAlerta: TIPO_ALERTA } }); + setChecked(new Set()); + invalidateAll(); + } catch { + alert('Error al restaurar'); + } + }; + + const handleChangeView = (next: 'activos' | 'descartados') => { + setView(next); + setChecked(new Set()); + }; + + const handleClearFilters = () => { + setFechaDesde(''); + setFechaHasta(''); + setTipoRelFilter(''); + }; + + const hasActiveFilters = fechaDesde || fechaHasta || tipoRelFilter; + const allChecked = sortedData && sortedData.length > 0 && checked.size === sortedData.length; + + return ( + + + +
+ + {view === 'activos' + ? 'Notas de crédito (E) que referencian un CFDI tratado como anticipo por otra factura — posible error de emisor (debería ser TipoRelacion 07)' + : 'CFDIs descartados manualmente — ignorados en la alerta'} + +
+
+ + +
+ {checked.size > 0 && view === 'activos' && ( + + )} + {checked.size > 0 && view === 'descartados' && ( + + )} + {data && data.length > 0 && ( + + )} +
+
+ +
+
+ + Filtros: +
+
+ + setFechaDesde(e.target.value)} + className="h-8 w-[150px] text-sm" + /> +
+
+ + setFechaHasta(e.target.value)} + className="h-8 w-[150px] text-sm" + /> +
+
+ + +
+ {hasActiveFilters && ( + + )} +
+
+ + {isLoading ? ( +
Cargando...
+ ) : !sortedData || sortedData.length === 0 ? ( +
+ {hasActiveFilters + ? 'No hay resultados con los filtros seleccionados' + : view === 'activos' + ? 'No hay CFDIs sospechosos' + : 'No hay CFDIs descartados'} +
+ ) : ( +
+ + + + + + toggleSort('fecha')} /> + + + + + toggleSort('total')} /> + + + + + {sortedData.map((cfdi: any) => { + const refs = (cfdi.cfdisRelacionados || '').split('|').filter(Boolean); + return ( + + + + + + + + + + + + ); + })} + +
+ + UUIDEmisorReceptorTipoRelReferenciados
+ + {cfdi.uuid?.substring(0, 8)}{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')} +
{cfdi.rfcEmisor}
+
{cfdi.nombreEmisor}
+
+
{cfdi.rfcReceptor}
+
{cfdi.nombreReceptor}
+
{cfdi.cfdiTipoRelacion} + {refs.map((u: string) => ( +
{u.substring(0, 8)}
+ ))} +
{formatCurrency(Number(cfdi.totalMxn))} + +
+

+ {sortedData.length} CFDI{sortedData.length !== 1 ? 's' : ''} {view === 'activos' ? 'sospechosos' : 'descartados'} + {hasActiveFilters && data && ` (de ${data.length} total)`} +

+
+ )} +
+
+ + setSelectedCfdi(null)} + /> +
+ ); +} diff --git a/apps/web/app/(dashboard)/calendario/page.tsx b/apps/web/app/(dashboard)/calendario/page.tsx new file mode 100644 index 0000000..1fa148e --- /dev/null +++ b/apps/web/app/(dashboard)/calendario/page.tsx @@ -0,0 +1,437 @@ +'use client'; + +import { useState } from 'react'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import { Card, CardContent, CardHeader, CardTitle, Button, Input, Label } from '@horux/shared-ui'; +import { useEventos, useCreateEvento, useUpdateEvento, useDeleteEvento } from '@/lib/hooks/use-calendario'; +import { useAuthStore } from '@/stores/auth-store'; +import { + Calendar, ChevronLeft, ChevronRight, Check, Clock, FileText, + CreditCard, Plus, X, Pencil, Trash2, Lock, Globe, AlertTriangle, +} from 'lucide-react'; +import { cn } from '@horux/shared-ui'; +import type { EventoFiscal } from '@horux/shared'; + +const meses = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; + +const tipoIcons: Record = { + declaracion: FileText, + pago: CreditCard, + obligacion: Clock, + informativa: FileText, + custom: Calendar, + 'obligacion-pendiente': Clock, + 'obligacion-completada': Check, + 'obligacion-atrasada': AlertTriangle, +}; + +const tipoColors: Record = { + declaracion: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', + pago: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + obligacion: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', + informativa: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200', + custom: 'bg-violet-100 text-violet-800 dark:bg-violet-900 dark:text-violet-200', + 'obligacion-pendiente': 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200', + 'obligacion-completada': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + 'obligacion-atrasada': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', +}; + +interface RecordatorioForm { + titulo: string; + descripcion: string; + fechaLimite: string; + notas: string; + privado: boolean; +} + +const emptyForm: RecordatorioForm = { + titulo: '', + descripcion: '', + fechaLimite: '', + notas: '', + privado: false, +}; + +export default function CalendarioPage() { + const [año, setAño] = useState(new Date().getFullYear()); + const [mes, setMes] = useState(new Date().getMonth() + 1); + const { data: eventos, isLoading } = useEventos(año); + const createEvento = useCreateEvento(); + const updateEvento = useUpdateEvento(); + const deleteEvento = useDeleteEvento(); + const { user } = useAuthStore(); + + const [showForm, setShowForm] = useState(false); + const [editingId, setEditingId] = useState(null); + const [form, setForm] = useState(emptyForm); + + const canEdit = ['owner', 'cfo', 'contador', 'auxiliar', 'supervisor'].includes(user?.role || ''); + + const handlePrevMonth = () => { + if (mes === 1) { setMes(12); setAño(año - 1); } + else setMes(mes - 1); + }; + + const handleNextMonth = () => { + if (mes === 12) { setMes(1); setAño(año + 1); } + else setMes(mes + 1); + }; + + const handleToggleComplete = (evento: EventoFiscal) => { + if (!evento.id) return; + if (evento.tipo === 'custom') { + updateEvento.mutate({ id: evento.id, data: { completado: !evento.completado } }); + } + }; + + const handleOpenCreate = () => { + setEditingId(null); + const defaultDate = `${año}-${String(mes).padStart(2, '0')}-15`; + setForm({ ...emptyForm, fechaLimite: defaultDate }); + setShowForm(true); + }; + + const handleOpenEdit = (evento: EventoFiscal) => { + if (!evento.id || evento.tipo !== 'custom') return; + setEditingId(evento.id); + setForm({ + titulo: evento.titulo, + descripcion: evento.descripcion || '', + fechaLimite: evento.fechaLimite, + notas: evento.notas || '', + privado: (evento as any).privado ?? false, + }); + setShowForm(true); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + if (editingId) { + await updateEvento.mutateAsync({ + id: editingId, + data: { titulo: form.titulo, descripcion: form.descripcion, fechaLimite: form.fechaLimite, notas: form.notas, privado: form.privado } as any, + }); + } else { + await createEvento.mutateAsync({ + titulo: form.titulo, + descripcion: form.descripcion, + tipo: 'custom', + fechaLimite: form.fechaLimite, + recurrencia: 'unica', + notas: form.notas, + privado: form.privado, + } as any); + } + setShowForm(false); + setForm(emptyForm); + setEditingId(null); + } catch { + alert('Error al guardar recordatorio'); + } + }; + + const handleDelete = async (id: number) => { + if (!confirm('¿Eliminar este recordatorio?')) return; + try { + await deleteEvento.mutateAsync(id); + } catch { + alert('Error al eliminar'); + } + }; + + const handleCancelForm = () => { + setShowForm(false); + setForm(emptyForm); + setEditingId(null); + }; + + // Generate calendar days + const firstDay = new Date(año, mes - 1, 1).getDay(); + const daysInMonth = new Date(año, mes, 0).getDate(); + const days = Array.from({ length: 42 }, (_, i) => { + const day = i - firstDay + 1; + if (day < 1 || day > daysInMonth) return null; + return day; + }); + + const getEventosForDay = (day: number) => { + return eventos?.filter(e => { + const fecha = new Date(e.fechaLimite + 'T00:00:00'); + return fecha.getFullYear() === año && fecha.getMonth() + 1 === mes && fecha.getDate() === day; + }) || []; + }; + + const eventosDelMes = eventos?.filter(e => { + const f = new Date(e.fechaLimite + 'T00:00:00'); + return f.getFullYear() === año && f.getMonth() + 1 === mes; + }) || []; + + return ( + + {/* Modal de crear/editar */} + {showForm && ( + + +
+ + {editingId ? 'Editar Recordatorio' : 'Nuevo Recordatorio'} + + +
+
+ +
+
+
+ + setForm({ ...form, titulo: e.target.value })} + placeholder="Reunión con contador" + required + /> +
+
+ + setForm({ ...form, fechaLimite: e.target.value })} + required + /> +
+
+
+ + setForm({ ...form, descripcion: e.target.value })} + placeholder="Revisión de declaración mensual" + /> +
+
+ + setForm({ ...form, notas: e.target.value })} + placeholder="Llevar estados de cuenta" + /> +
+
+ + + {form.privado ? 'Solo tú puedes verlo' : 'Visible para todo el equipo'} + +
+
+ + +
+
+
+
+ )} + +
+ {/* Calendar */} + + + + + {meses[mes - 1]} {año} + +
+ {canEdit && !showForm && ( + + )} + + +
+
+ + {/* Leyenda de colores por estado de obligación */} +
+ + + Pendiente + + + + Completada + + + + Atrasada + + + + Recordatorio custom + +
+
+ {['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'].map(d => ( +
+ {d} +
+ ))} + {days.map((day, i) => { + const dayEventos = day ? getEventosForDay(day) : []; + const isToday = day === new Date().getDate() && mes === new Date().getMonth() + 1 && año === new Date().getFullYear(); + return ( +
+ {day && ( + <> +
{day}
+
+ {dayEventos.slice(0, 2).map((e, idx) => { + const Icon = tipoIcons[e.tipo] || Calendar; + return ( +
e.tipo === 'custom' && canEdit && handleOpenEdit(e)} + > + + {e.titulo} +
+ ); + })} + {dayEventos.length > 2 && ( +
+{dayEventos.length - 2} más
+ )} +
+ + )} +
+ ); + })} +
+
+
+ + {/* Event List */} + + + Eventos del Mes + + + {isLoading ? ( +
Cargando...
+ ) : eventosDelMes.length === 0 ? ( +
No hay eventos este mes
+ ) : ( +
+ {eventosDelMes.map((evento, idx) => { + const Icon = tipoIcons[evento.tipo] || FileText; + const isCustom = evento.tipo === 'custom'; + return ( +
+
+
+ +
+
+
+

+ {evento.titulo} +

+ {isCustom && (evento as any).privado && ( + + )} +
+ {evento.descripcion && ( +

{evento.descripcion}

+ )} +

+ {new Date(evento.fechaLimite + 'T00:00:00').toLocaleDateString('es-MX', { + day: 'numeric', + month: 'short', + })} +

+
+ {isCustom && canEdit && ( +
+ + + +
+ )} +
+
+ ); + })} +
+ )} +
+
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/carteras/page.tsx b/apps/web/app/(dashboard)/carteras/page.tsx new file mode 100644 index 0000000..2f24dda --- /dev/null +++ b/apps/web/app/(dashboard)/carteras/page.tsx @@ -0,0 +1,539 @@ +'use client'; + +import { useState } from 'react'; +import { + Button, Card, CardContent, CardHeader, CardTitle, Input, Label, + Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, + Select, SelectContent, SelectItem, SelectTrigger, SelectValue, + cn, +} from '@horux/shared-ui'; +import { useQueryClient } from '@tanstack/react-query'; +import { + FolderOpen, Plus, Trash2, ChevronDown, ChevronUp, X, + Users, Building2, FolderPlus, UserCog, +} from 'lucide-react'; +import { + useCarteras, useCreateCartera, useDeleteCartera, + useCarteraEntidades, useSubcarteras, useCreateSubcartera, + useSupervisores, +} from '@/lib/hooks/use-carteras'; +import { + addEntidadToCartera, removeEntidadFromCartera, +} from '@/lib/api/carteras'; +import { useContribuyentes } from '@/lib/hooks/use-contribuyentes'; +import { useUsuarios } from '@/lib/hooks/use-usuarios'; +import { useAuthStore } from '@/stores/auth-store'; +import { DashboardShell } from '@/components/layouts/dashboard-shell'; +import type { Cartera } from '@/lib/api/carteras'; + +/* ------------------------------------------------------------------ */ +/* SubcarteraCard */ +/* ------------------------------------------------------------------ */ +function SubcarteraCard({ sub, usuarios, contribuyentes, onDelete }: { + sub: Cartera; + usuarios: any[]; + contribuyentes: any[]; + onDelete: () => void; +}) { + const [expanded, setExpanded] = useState(false); + const qc = useQueryClient(); + const { data: entidadIds, isLoading } = useCarteraEntidades(expanded ? sub.id : null); + const [addingEntidad, setAddingEntidad] = useState(false); + const [selectedEntidadId, setSelectedEntidadId] = useState(''); + const [busy, setBusy] = useState(false); + + const entidadMap = Object.fromEntries( + (contribuyentes ?? []).map((c: any) => [c.id, { rfc: c.rfc, nombre: c.nombre }]) + ); + + const available = (contribuyentes ?? []).filter( + (c: any) => !(entidadIds ?? []).includes(c.id) + ); + + const auxiliarUser = usuarios?.find((u: any) => u.id === sub.auxiliarUserId); + + const invalidate = () => { + qc.invalidateQueries({ queryKey: ['cartera-entidades', sub.id] }); + qc.invalidateQueries({ queryKey: ['subcarteras'] }); + qc.invalidateQueries({ queryKey: ['carteras'] }); + }; + + const handleAddEntidad = async () => { + if (!selectedEntidadId) return; + setBusy(true); + try { + await addEntidadToCartera(sub.id, selectedEntidadId); + setSelectedEntidadId(''); + setAddingEntidad(false); + invalidate(); + } finally { setBusy(false); } + }; + + const handleRemoveEntidad = async (entidadId: string) => { + setBusy(true); + try { + await removeEntidadFromCartera(sub.id, entidadId); + invalidate(); + } finally { setBusy(false); } + }; + + return ( +
+
+ + +
+ + {expanded && ( +
+ {!addingEntidad && ( + + )} + {addingEntidad && ( +
+ + + +
+ )} + {isLoading ? ( +

Cargando...

+ ) : !entidadIds || entidadIds.length === 0 ? ( +

Sin RFCs asignados a esta subcartera.

+ ) : ( +
    + {entidadIds.map(id => { + const info = entidadMap[id]; + return ( +
  • + {info ? <>{info.rfc} {info.nombre} : id} + +
  • + ); + })} +
+ )} +
+ )} +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* CarteraDetail */ +/* ------------------------------------------------------------------ */ +function CarteraDetail({ cartera, canEdit = true, canManageSubcarteras = true }: { cartera: Cartera; canEdit?: boolean; canManageSubcarteras?: boolean }) { + const qc = useQueryClient(); + const { data: contribuyentes } = useContribuyentes(); + const { data: usuarios } = useUsuarios(); + const { data: entidadIds, isLoading: loadingEntidades } = useCarteraEntidades(cartera.id); + const { data: subcarteras, isLoading: loadingSubs } = useSubcarteras(cartera.id); + const createSub = useCreateSubcartera(); + + const [addingEntidad, setAddingEntidad] = useState(false); + const [selectedEntidadId, setSelectedEntidadId] = useState(''); + const [showCreateSub, setShowCreateSub] = useState(false); + const [subForm, setSubForm] = useState({ nombre: '', auxiliarUserId: '' }); + const [busy, setBusy] = useState(false); + + const entidadMap = Object.fromEntries( + (contribuyentes ?? []).map((c) => [c.id, { rfc: c.rfc, nombre: c.nombre }]) + ); + + const available = (contribuyentes ?? []).filter( + (c) => !(entidadIds ?? []).includes(c.id) + ); + + // Auxiliares available for subcarteras (those assigned to this supervisor) + const auxiliares = (usuarios ?? []).filter((u: any) => u.role === 'auxiliar'); + + const supervisorUser = usuarios?.find((u: any) => u.id === cartera.supervisorUserId); + + const invalidate = () => { + qc.invalidateQueries({ queryKey: ['cartera-entidades', cartera.id] }); + qc.invalidateQueries({ queryKey: ['subcarteras', cartera.id] }); + qc.invalidateQueries({ queryKey: ['carteras'] }); + }; + + const handleAddEntidad = async () => { + if (!selectedEntidadId) return; + setBusy(true); + try { + await addEntidadToCartera(cartera.id, selectedEntidadId); + setSelectedEntidadId(''); + setAddingEntidad(false); + invalidate(); + } finally { setBusy(false); } + }; + + const handleRemoveEntidad = async (entidadId: string) => { + setBusy(true); + try { + await removeEntidadFromCartera(cartera.id, entidadId); + invalidate(); + } finally { setBusy(false); } + }; + + const handleCreateSubcartera = async () => { + if (!subForm.nombre.trim() || !subForm.auxiliarUserId) return; + try { + await createSub.mutateAsync({ + carteraId: cartera.id, + nombre: subForm.nombre.trim(), + auxiliarUserId: subForm.auxiliarUserId, + }); + setSubForm({ nombre: '', auxiliarUserId: '' }); + setShowCreateSub(false); + } catch (err: any) { + alert(err.response?.data?.message || 'Error al crear subcartera'); + } + }; + + const handleDeleteSubcartera = async (subId: string) => { + if (!confirm('¿Eliminar esta subcartera?')) return; + try { + const { deleteCartera } = await import('@/lib/api/carteras'); + await deleteCartera(subId); + invalidate(); + } catch (err: any) { + alert(err.response?.data?.message || 'Error al eliminar'); + } + }; + + return ( +
+ {/* Supervisor info */} + {supervisorUser && ( +
+ + Supervisor: {supervisorUser.nombre} ({supervisorUser.email}) +
+ )} + + {/* ---- Contribuyentes ---- */} +
+
+

+ + Contribuyentes ({entidadIds?.length || 0}) +

+ {canEdit && !addingEntidad && ( + + )} +
+ + {canEdit && addingEntidad && ( +
+ + + +
+ )} + + {loadingEntidades ? ( +

Cargando...

+ ) : !entidadIds || entidadIds.length === 0 ? ( +

Sin contribuyentes asignados.

+ ) : ( +
    + {entidadIds.map(id => { + const info = entidadMap[id]; + return ( +
  • + {info ? <>{info.rfc} {info.nombre} : {id}} + {canEdit && } +
  • + ); + })} +
+ )} +
+ + {/* ---- Subcarteras ---- */} +
+
+

+ + Subcarteras ({subcarteras?.length || 0}) +

+ {canManageSubcarteras && !showCreateSub && ( + + )} +
+ + {canManageSubcarteras && showCreateSub && ( +
+
+
+ + setSubForm(p => ({ ...p, nombre: e.target.value }))} placeholder="Ej. Cartera de María" className="h-8 text-sm mt-1" /> +
+
+ + +
+
+
+ + +
+
+ )} + + {loadingSubs ? ( +

Cargando...

+ ) : !subcarteras || subcarteras.length === 0 ? ( +

Sin subcarteras. Crea una para asignar RFCs a un auxiliar.

+ ) : ( +
+ {subcarteras.map(sub => ( + handleDeleteSubcartera(sub.id)} + /> + ))} +
+ )} +
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* CarteraCard */ +/* ------------------------------------------------------------------ */ +function CarteraCard({ cartera, expanded, onToggle, onDelete, usuarios, canEdit, canManageSubcarteras }: { + cartera: Cartera; + expanded: boolean; + onToggle: () => void; + onDelete: () => void; + usuarios: any[]; + canEdit: boolean; + canManageSubcarteras: boolean; +}) { + const supervisorUser = usuarios?.find((u: any) => u.id === cartera.supervisorUserId); + return ( + + +
+ + {canEdit && ( + + )} +
+
+ {supervisorUser && ( + + + {supervisorUser.nombre} + + )} + + + {cartera.entidadesCount} RFCs + + + + {cartera.subcarterasCount} subcarteras + +
+
+ {expanded && ( + + + + )} +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Page */ +/* ------------------------------------------------------------------ */ +export default function CarterasPage() { + const { user } = useAuthStore(); + const userRole = user?.role || 'visor'; + const canCreate = userRole === 'owner'; // Create top-level carteras + const canEditCartera = userRole === 'owner'; // Edit/delete top-level carteras + add/remove RFCs + const canManageSubcarteras = userRole === 'owner' || userRole === 'supervisor'; // Create subcarteras + const isAuxiliar = userRole === 'auxiliar'; + const { data: carteras, isLoading } = useCarteras(); + const { data: supervisores } = useSupervisores(); + const { data: usuarios } = useUsuarios(); + const createMut = useCreateCartera(); + const deleteMut = useDeleteCartera(); + + const [expandedId, setExpandedId] = useState(null); + const [showCreate, setShowCreate] = useState(false); + const [form, setForm] = useState({ nombre: '', descripcion: '', supervisorUserId: '' }); + + const hasSupervisores = supervisores && supervisores.length > 0; + + const resetForm = () => { + setForm({ nombre: '', descripcion: '', supervisorUserId: '' }); + setShowCreate(false); + }; + + const handleCreate = async () => { + if (!form.nombre.trim()) return; + try { + const supervisorUserId = form.supervisorUserId && form.supervisorUserId !== '__self__' + ? form.supervisorUserId : undefined; + const cartera = await createMut.mutateAsync({ + nombre: form.nombre.trim(), + descripcion: form.descripcion.trim() || undefined, + supervisorUserId, + }); + resetForm(); + setExpandedId(cartera.id); + } catch (err: any) { + alert(err.response?.data?.message || 'Error al crear cartera'); + } + }; + + const handleDelete = async (cartera: Cartera) => { + if (!confirm(`¿Eliminar la cartera "${cartera.nombre}"? Se eliminarán también sus subcarteras.`)) return; + try { + await deleteMut.mutateAsync(cartera.id); + if (expandedId === cartera.id) setExpandedId(null); + } catch (err: any) { + alert(err.response?.data?.message || 'Error al eliminar cartera'); + } + }; + + return ( + +
+ {/* Header */} +
+
+

+ {isAuxiliar ? 'Carteras asignadas a ti' : 'Organiza contribuyentes en carteras y asigna subcarteras a cada auxiliar'} +

+
+ {canCreate && ( + + )} +
+ + {/* List */} + {isLoading ? ( +

Cargando...

+ ) : !carteras || carteras.length === 0 ? ( + + + +

Sin carteras

+

+ Crea la primera cartera para organizar tus contribuyentes. +

+ +
+
+ ) : ( +
+ {carteras.map(cartera => ( + setExpandedId(expandedId === cartera.id ? null : cartera.id)} + onDelete={() => handleDelete(cartera)} + usuarios={usuarios ?? []} + canEdit={canEditCartera} + canManageSubcarteras={canManageSubcarteras} + /> + ))} +
+ )} + + {/* Create dialog */} + { if (!open) resetForm(); }}> + + + Nueva cartera + +
+
+ + setForm(p => ({ ...p, nombre: e.target.value }))} placeholder="Ej. Clientes CDMX" autoFocus /> +
+
+ + setForm(p => ({ ...p, descripcion: e.target.value }))} placeholder="Descripcion breve" /> +
+ {hasSupervisores ? ( +
+ + +

Si no seleccionas, la cartera se asigna a ti.

+
+ ) : ( +

+ No hay supervisores registrados. La cartera se asignará a ti como owner. +

+ )} +
+ + + + +
+
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/cfdi/page.tsx b/apps/web/app/(dashboard)/cfdi/page.tsx new file mode 100644 index 0000000..bc0c127 --- /dev/null +++ b/apps/web/app/(dashboard)/cfdi/page.tsx @@ -0,0 +1,2211 @@ +'use client'; + +import { useState, useRef, useCallback, useEffect } from 'react'; +import { useDebounce } from '@horux/shared-ui'; +import { Header } from '@/components/layouts/header'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Popover, PopoverTrigger, PopoverContent } from '@horux/shared-ui'; +import { useCfdis, useCreateCfdi, useDeleteCfdi } from '@/lib/hooks/use-cfdi'; +import { createManyCfdis, searchEmisores, searchReceptores, getCfdis, getConceptosList, type EmisorReceptor } from '@/lib/api/cfdi'; +import { cancelarFactura, downloadPdf } from '@/lib/api/facturacion'; +import type { CfdiFilters, TipoCfdi, Cfdi } from '@horux/shared'; +import type { CreateCfdiData } from '@/lib/api/cfdi'; +import { FileText, Search, ChevronLeft, ChevronRight, Plus, Upload, Trash2, X, FileUp, CheckCircle, AlertCircle, Loader2, Eye, Filter, XCircle, Calendar, User, Building2, Download, Printer } from 'lucide-react'; +import * as XLSX from 'xlsx'; +import { saveAs } from 'file-saver'; +import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; +import { getCfdiById } from '@/lib/api/cfdi'; +import { useAuthStore } from '@/stores/auth-store'; +import { useTenantViewStore } from '@/stores/tenant-view-store'; +import { useContribuyenteStore } from '@/stores/contribuyente-store'; +import { useQueryClient, useQuery } from '@tanstack/react-query'; + +// Upload progress state +interface UploadProgress { + status: 'idle' | 'parsing' | 'uploading' | 'complete' | 'error'; + totalFiles: number; + parsedFiles: number; + validFiles: number; + currentBatch: number; + totalBatches: number; + uploaded: number; + duplicates: number; + errors: number; + errorMessages: string[]; +} + +type CfdiTipo = 'EMITIDO' | 'RECIBIDO'; + +const initialFormData: CreateCfdiData = { + uuid: '', + type: 'EMITIDO', + serie: '', + folio: '', + fechaEmision: new Date().toISOString().split('T')[0], + rfcEmisor: '', + nombreEmisor: '', + rfcReceptor: '', + nombreReceptor: '', + subtotal: 0, + descuento: 0, + ivaTraslado: 0, + isrRetencion: 0, + ivaRetencion: 0, + total: 0, + moneda: 'MXN', + metodoPago: 'PUE', + formaPago: '03', + usoCfdi: 'G03', +}; + +// Helper function to find element regardless of namespace prefix +function findElement(doc: Document, localName: string): Element | null { + // Try common prefixes first (most reliable for CFDI) + const prefixes = ['cfdi', 'tfd', 'pago20', 'pago10', 'nomina12', '']; + for (const prefix of prefixes) { + const tagName = prefix ? `${prefix}:${localName}` : localName; + const el = doc.getElementsByTagName(tagName)[0] as Element; + if (el) return el; + } + + // Try with wildcard - search all elements by localName + const elements = doc.getElementsByTagName('*'); + for (let i = 0; i < elements.length; i++) { + if (elements[i].localName === localName) { + return elements[i]; + } + } + + return null; +} + +// Parse CFDI XML and extract data +function parseCfdiXml(xmlString: string, tenantRfc: string): CreateCfdiData | null { + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(xmlString, 'text/xml'); + + // Check for parse errors + const parseError = doc.querySelector('parsererror'); + if (parseError) { + console.error('XML parse error:', parseError.textContent); + return null; + } + + // Get the Comprobante element (root) + const comprobante = findElement(doc, 'Comprobante'); + if (!comprobante) { + console.error('No se encontro elemento Comprobante'); + return null; + } + + // Get TimbreFiscalDigital for UUID + const timbre = findElement(doc, 'TimbreFiscalDigital'); + const uuid = timbre?.getAttribute('UUID') || ''; + const fechaTimbradoRaw = timbre?.getAttribute('FechaTimbrado') || ''; + + // Get Emisor + const emisor = findElement(doc, 'Emisor'); + const rfcEmisor = emisor?.getAttribute('Rfc') || emisor?.getAttribute('rfc') || ''; + const nombreEmisor = emisor?.getAttribute('Nombre') || emisor?.getAttribute('nombre') || ''; + + // Get Receptor + const receptor = findElement(doc, 'Receptor'); + const rfcReceptor = receptor?.getAttribute('Rfc') || receptor?.getAttribute('rfc') || ''; + const nombreReceptor = receptor?.getAttribute('Nombre') || receptor?.getAttribute('nombre') || ''; + const usoCfdi = receptor?.getAttribute('UsoCFDI') || ''; + + // Determine type based on tenant RFC + // If tenant is emisor -> ingreso (we issued the invoice) + // If tenant is receptor -> egreso (we received the invoice) + const tenantRfcUpper = tenantRfc.toUpperCase(); + let tipoFinal: CreateCfdiData['type']; + if (rfcEmisor.toUpperCase() === tenantRfcUpper) { + tipoFinal = 'EMITIDO'; + } else { + tipoFinal = 'RECIBIDO'; + } + + // Get impuestos - search for the Impuestos element that is direct child of Comprobante + // (not the ones inside Conceptos) + let totalImpuestosTrasladados = 0; + let totalImpuestosRetenidos = 0; + + // Try to get TotalImpuestosTrasladados from Comprobante's direct Impuestos child + const allImpuestos = doc.getElementsByTagName('*'); + for (let i = 0; i < allImpuestos.length; i++) { + const el = allImpuestos[i]; + if (el.localName === 'Impuestos' && el.parentElement?.localName === 'Comprobante') { + totalImpuestosTrasladados = parseFloat(el.getAttribute('TotalImpuestosTrasladados') || '0'); + totalImpuestosRetenidos = parseFloat(el.getAttribute('TotalImpuestosRetenidos') || '0'); + break; + } + } + + // Fallback: calculate IVA from total - subtotal if not found + const subtotal = parseFloat(comprobante.getAttribute('SubTotal') || '0'); + const descuento = parseFloat(comprobante.getAttribute('Descuento') || '0'); + const total = parseFloat(comprobante.getAttribute('Total') || '0'); + + if (totalImpuestosTrasladados === 0 && total > subtotal) { + totalImpuestosTrasladados = Math.max(0, total - subtotal + descuento + totalImpuestosRetenidos); + } + + // Get retenciones breakdown + let isrRetenido = 0; + let ivaRetenido = 0; + const retenciones = doc.querySelectorAll('[localName="Retencion"], Retencion, cfdi\\:Retencion'); + retenciones.forEach((ret: Element) => { + const impuesto = ret.getAttribute('Impuesto'); + const importe = parseFloat(ret.getAttribute('Importe') || '0'); + if (impuesto === '001') isrRetenido = importe; // ISR + if (impuesto === '002') ivaRetenido = importe; // IVA + }); + + // Parse dates - handle both ISO format and datetime format + const fechaEmisionRaw = comprobante.getAttribute('Fecha') || ''; + const fechaEmision = fechaEmisionRaw.includes('T') ? fechaEmisionRaw.split('T')[0] : fechaEmisionRaw; + const fechaTimbrado = fechaTimbradoRaw.includes('T') ? fechaTimbradoRaw.split('T')[0] : fechaTimbradoRaw; + + // Validate required fields + if (!uuid) { + console.error('UUID no encontrado en el XML'); + return null; + } + if (!rfcEmisor || !rfcReceptor) { + console.error('RFC emisor o receptor no encontrado'); + return null; + } + if (!fechaEmision) { + console.error('Fecha de emision no encontrada'); + return null; + } + + return { + uuid: uuid.toUpperCase(), + type: tipoFinal, + serie: comprobante.getAttribute('Serie') || '', + folio: comprobante.getAttribute('Folio') || '', + fechaEmision, + rfcEmisor, + nombreEmisor: nombreEmisor || 'Sin nombre', + rfcReceptor, + nombreReceptor: nombreReceptor || 'Sin nombre', + subtotal, + descuento, + ivaTraslado: totalImpuestosTrasladados, + isrRetencion: isrRetenido, + ivaRetencion: ivaRetenido, + total, + moneda: comprobante.getAttribute('Moneda') || 'MXN', + tipoCambio: parseFloat(comprobante.getAttribute('TipoCambio') || '1'), + tipoComprobante: comprobante.getAttribute('TipoDeComprobante') || '', + metodoPago: comprobante.getAttribute('MetodoPago') || '', + formaPago: comprobante.getAttribute('FormaPago') || '', + usoCfdi, + }; + } catch (error) { + console.error('Error parsing XML:', error); + return null; + } +} + +const TIPO_COMPROBANTE_LABELS: Record = { + I: 'Ingreso', + E: 'Egreso', + P: 'Pago', + T: 'Traslado', + N: 'Nómina', +}; + +function formatTipoComprobante(value: string | null | undefined): string { + if (!value) return ''; + const upper = value.toUpperCase(); + return TIPO_COMPROBANTE_LABELS[upper] ? `${upper} - ${TIPO_COMPROBANTE_LABELS[upper]}` : upper; +} + +// Chunk size for batch uploads +const PARSE_CHUNK_SIZE = 500; // Parse 500 files at a time +const UPLOAD_CHUNK_SIZE = 200; // Upload 200 CFDIs per request + +export default function CfdiPage() { + const { user } = useAuthStore(); + const { viewingTenantRfc } = useTenantViewStore(); + const { selectedContribuyenteId } = useContribuyenteStore(); + const fileInputRef = useRef(null); + const queryClient = useQueryClient(); + + // Get the effective tenant RFC (viewing tenant or user's tenant) + const tenantRfc = viewingTenantRfc || user?.tenantRfc || ''; + const [filters, setFilters] = useState({ + page: 1, + limit: 20, + }); + + const [searchTerm, setSearchTerm] = useState(''); + const [columnFilters, setColumnFilters] = useState({ + fechaInicio: '', + fechaFin: '', + emisor: '', + receptor: '', + }); + + // Reset pagination and filters when contribuyente changes + useEffect(() => { + setFilters({ page: 1, limit: 20 }); + setSearchTerm(''); + setColumnFilters({ fechaInicio: '', fechaFin: '', emisor: '', receptor: '' }); + }, [selectedContribuyenteId]); + const [openFilter, setOpenFilter] = useState<'fecha' | 'emisor' | 'receptor' | null>(null); + const [emisorSuggestions, setEmisorSuggestions] = useState([]); + const [receptorSuggestions, setReceptorSuggestions] = useState([]); + const [loadingEmisor, setLoadingEmisor] = useState(false); + const [loadingReceptor, setLoadingReceptor] = useState(false); + const [showForm, setShowForm] = useState(false); + + // Debounced values for autocomplete + const debouncedEmisor = useDebounce(columnFilters.emisor, 300); + const debouncedReceptor = useDebounce(columnFilters.receptor, 300); + + // Fetch emisor suggestions when debounced value changes + useEffect(() => { + if (debouncedEmisor.length < 2) { + setEmisorSuggestions([]); + return; + } + setLoadingEmisor(true); + searchEmisores(debouncedEmisor) + .then(setEmisorSuggestions) + .catch(() => setEmisorSuggestions([])) + .finally(() => setLoadingEmisor(false)); + }, [debouncedEmisor]); + + // Fetch receptor suggestions when debounced value changes + useEffect(() => { + if (debouncedReceptor.length < 2) { + setReceptorSuggestions([]); + return; + } + setLoadingReceptor(true); + searchReceptores(debouncedReceptor) + .then(setReceptorSuggestions) + .catch(() => setReceptorSuggestions([])) + .finally(() => setLoadingReceptor(false)); + }, [debouncedReceptor]); + + const [showBulkForm, setShowBulkForm] = useState(false); + const [formData, setFormData] = useState(initialFormData); + const [bulkData, setBulkData] = useState(''); + const [uploadMode, setUploadMode] = useState<'xml' | 'json'>('xml'); + const [jsonUploading, setJsonUploading] = useState(false); + + // Optimized upload state + const [uploadProgress, setUploadProgress] = useState({ + status: 'idle', + totalFiles: 0, + parsedFiles: 0, + validFiles: 0, + currentBatch: 0, + totalBatches: 0, + uploaded: 0, + duplicates: 0, + errors: 0, + errorMessages: [] + }); + const [parsedCfdis, setParsedCfdis] = useState([]); + const uploadAbortRef = useRef(false); + + const { data, isLoading } = useCfdis(filters); + + // Pestañas: CFDIs (lista actual) | Conceptos (tabla cross-CFDI con conceptos). + // Conceptos hereda los mismos filtros aplicados a CFDIs + tiene filtros propios. + const [activeTab, setActiveTab] = useState<'cfdis' | 'conceptos'>('cfdis'); + // Filtros locales de la pestaña Conceptos (no compartidos con CFDIs). + // Popovers en headers UUID, Clave, Descripción + ordenamiento por importe. + const [conceptosFilters, setConceptosFilters] = useState<{ + uuidLike: string; + claveProdServ: string; + descripcionConcepto: string; + orderBy?: 'fecha' | 'importe'; + orderDir?: 'asc' | 'desc'; + }>({ uuidLike: '', claveProdServ: '', descripcionConcepto: '' }); + const [conceptosOpenFilter, setConceptosOpenFilter] = useState<'uuid' | 'clave' | 'descripcion' | null>(null); + + const conceptosQuery = useQuery({ + queryKey: ['cfdi-conceptos', filters, selectedContribuyenteId, conceptosFilters], + queryFn: () => getConceptosList({ + ...filters, + contribuyenteId: selectedContribuyenteId || undefined, + uuidLike: conceptosFilters.uuidLike || undefined, + claveProdServ: conceptosFilters.claveProdServ || undefined, + descripcionConcepto: conceptosFilters.descripcionConcepto || undefined, + orderBy: conceptosFilters.orderBy, + orderDir: conceptosFilters.orderDir, + }), + enabled: activeTab === 'conceptos', + }); + + const toggleImporteSort = () => { + setConceptosFilters(prev => { + // null → asc → desc → null (o ciclo simple asc ↔ desc si prefieres) + const isImporte = prev.orderBy === 'importe'; + if (!isImporte) return { ...prev, orderBy: 'importe', orderDir: 'desc' }; + if (prev.orderDir === 'desc') return { ...prev, orderBy: 'importe', orderDir: 'asc' }; + return { ...prev, orderBy: undefined, orderDir: undefined }; + }); + setFilters(f => ({ ...f, page: 1 })); + }; + const createCfdi = useCreateCfdi(); + const deleteCfdi = useDeleteCfdi(); + + // CFDI Viewer state + const [viewingCfdi, setViewingCfdi] = useState(null); + const [loadingCfdi, setLoadingCfdi] = useState(null); + + // Cancelación Facturapi state + const [cancelTarget, setCancelTarget] = useState(null); + const [cancelMotive, setCancelMotive] = useState<'01' | '02' | '03' | '04'>('02'); + const [cancelSubstitution, setCancelSubstitution] = useState(''); + const [cancelling, setCancelling] = useState(false); + + const handleViewCfdi = async (id: string) => { + setLoadingCfdi(id); + try { + const cfdi = await getCfdiById(id); + setViewingCfdi(cfdi); + } catch (error) { + console.error('Error loading CFDI:', error); + alert('Error al cargar el CFDI'); + } finally { + setLoadingCfdi(null); + } + }; + + const canEdit = user?.role === 'owner' || user?.role === 'cfo' || user?.role === 'contador' || user?.role === 'auxiliar'; + + const handleSearch = () => { + setFilters({ ...filters, search: searchTerm, page: 1 }); + }; + + // Export to Excel + const [exporting, setExporting] = useState(false); + + const exportToExcel = async () => { + if (!data?.data.length) return; + + setExporting(true); + try { + // Fetch TODOS los CFDIs que cumplen los filtros (no solo la página visible). + // Topamos a 10,000 filas — Excel maneja 1M, pero >10k es más reporte que + // exploración, conviene empujar al user a filtrar más fino. + // NOTA: el hook `useCfdis` inyecta contribuyenteId automáticamente; al + // bypassearlo aquí (fetch directo) hay que inyectarlo manualmente o el + // export trae CFDIs de TODO el despacho en lugar del contribuyente activo. + const EXPORT_MAX = 10_000; + const fullResponse = await getCfdis({ + ...filters, + contribuyenteId: selectedContribuyenteId || undefined, + page: 1, + limit: EXPORT_MAX, + }); + const allRows = fullResponse.data; + + if (fullResponse.total > EXPORT_MAX) { + const proceed = confirm( + `Hay ${fullResponse.total.toLocaleString('es-MX')} CFDIs que cumplen los filtros, ` + + `pero el export está topado a ${EXPORT_MAX.toLocaleString('es-MX')} filas (los más recientes). ` + + `Ajusta filtros para precisar. ¿Continuar con las primeras ${EXPORT_MAX.toLocaleString('es-MX')} filas?` + ); + if (!proceed) { setExporting(false); return; } + } + + const exportData = allRows.map(cfdi => ({ + 'Fecha Emisión': new Date(cfdi.fechaEmision).toLocaleDateString('es-MX'), + 'Tipo Comprobante': formatTipoComprobante(cfdi.tipoComprobante), + 'Uso CFDI': (cfdi as any).usoCfdi || '', + 'Serie': cfdi.serie || '', + 'Folio': cfdi.folio || '', + 'RFC Emisor': cfdi.rfcEmisor, + 'Nombre Emisor': cfdi.nombreEmisor, + 'RFC Receptor': cfdi.rfcReceptor, + 'Nombre Receptor': cfdi.nombreReceptor, + 'Subtotal': cfdi.subtotal, + 'Descuento': cfdi.descuento || 0, + 'IVA': cfdi.ivaTraslado, + 'Total': cfdi.total, + 'Moneda': cfdi.moneda, + 'Método Pago': cfdi.metodoPago || '', + // Usamos `saldoPendienteMxn` (valor calculado por utils/saldo.ts) porque + // `saldoPendiente` (moneda original) no se backfilleó — todos NULL. + // PUE / P / E no tienen saldo conceptual → null en BD; lo dejamos + // vacío en Excel para no confundir "0 = pagado" con "no aplica". + 'Saldo Pendiente': cfdi.saldoPendienteMxn ?? '', + 'Estatus': cfdi.status === 'Vigente' || cfdi.status === '1' ? 'Vigente' : 'Cancelado', + 'Fecha Cancelación': cfdi.fechaCancelacion + ? new Date(cfdi.fechaCancelacion).toLocaleDateString('es-MX') + : '', + 'UUID': cfdi.uuid, + })); + + const ws = XLSX.utils.json_to_sheet(exportData); + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, 'CFDIs'); + + // Auto-size columns + const colWidths = Object.keys(exportData[0]).map(key => ({ + wch: Math.max(key.length, ...exportData.map(row => String(row[key as keyof typeof row]).length)) + })); + ws['!cols'] = colWidths; + + const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); + const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); + + const fileName = `cfdis_${new Date().toISOString().split('T')[0]}.xlsx`; + saveAs(blob, fileName); + } catch (error) { + console.error('Error exporting:', error); + alert('Error al exportar'); + } finally { + setExporting(false); + } + }; + + // Export de la pestaña Conceptos: trae todos los conceptos que cumplen los + // filtros actuales, descartando todas las columnas que terminan en `_mxn`. + const exportConceptosToExcel = async () => { + setExporting(true); + try { + const EXPORT_MAX = 10_000; + const fullResponse = await getConceptosList({ + ...filters, + contribuyenteId: selectedContribuyenteId || undefined, + uuidLike: conceptosFilters.uuidLike || undefined, + claveProdServ: conceptosFilters.claveProdServ || undefined, + descripcionConcepto: conceptosFilters.descripcionConcepto || undefined, + orderBy: conceptosFilters.orderBy, + orderDir: conceptosFilters.orderDir, + page: 1, + limit: EXPORT_MAX, + }); + const allRows = fullResponse.data; + if (!allRows.length) { alert('No hay conceptos que cumplan los filtros'); setExporting(false); return; } + + if (fullResponse.total > EXPORT_MAX) { + const proceed = confirm( + `Hay ${fullResponse.total.toLocaleString('es-MX')} conceptos que cumplen los filtros, ` + + `pero el export está topado a ${EXPORT_MAX.toLocaleString('es-MX')} filas. ` + + `¿Continuar con las primeras ${EXPORT_MAX.toLocaleString('es-MX')}?` + ); + if (!proceed) { setExporting(false); return; } + } + + // Filtrar columnas: quitar todas las que terminan en _mxn (per requerimiento). + // También quitamos `id`/`cfdi_id` (internas, sin valor para el contador). + const exportData = allRows.map(row => { + const out: Record = {}; + for (const [key, val] of Object.entries(row)) { + if (key.endsWith('_mxn') || key === 'id' || key === 'cfdi_id') continue; + // Formatear fecha si aplica + if (key === 'fechaEmision' && typeof val === 'string') { + out['Fecha Emisión'] = new Date(val).toLocaleDateString('es-MX'); + } else { + out[key] = val; + } + } + return out; + }); + + const ws = XLSX.utils.json_to_sheet(exportData); + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, 'Conceptos'); + + const colWidths = Object.keys(exportData[0]).map(key => ({ + wch: Math.max(key.length, ...exportData.map(row => String(row[key as keyof typeof row] ?? '').length)) + })); + ws['!cols'] = colWidths; + + const buf = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); + const blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); + saveAs(blob, `cfdi_conceptos_${new Date().toISOString().split('T')[0]}.xlsx`); + } catch (error) { + console.error('Error exportando conceptos:', error); + alert('Error al exportar conceptos'); + } finally { + setExporting(false); + } + }; + + const exportSingleCfdiToExcel = (cfdi: Cfdi) => { + const row = { + 'Fecha Emisión': new Date(cfdi.fechaEmision).toLocaleDateString('es-MX'), + 'Tipo Comprobante': formatTipoComprobante(cfdi.tipoComprobante), + 'Uso CFDI': (cfdi as any).usoCfdi || '', + 'Serie': cfdi.serie || '', + 'Folio': cfdi.folio || '', + 'RFC Emisor': cfdi.rfcEmisor, + 'Nombre Emisor': cfdi.nombreEmisor, + 'RFC Receptor': cfdi.rfcReceptor, + 'Nombre Receptor': cfdi.nombreReceptor, + 'Subtotal': cfdi.subtotal, + 'Descuento': cfdi.descuento || 0, + 'IVA': cfdi.ivaTraslado, + 'Total': cfdi.total, + 'Moneda': cfdi.moneda, + 'Método Pago': cfdi.metodoPago || '', + // Usamos `saldoPendienteMxn` (valor calculado por utils/saldo.ts) porque + // `saldoPendiente` (moneda original) no se backfilleó — todos NULL. + // PUE / P / E no tienen saldo conceptual → null en BD; lo dejamos + // vacío en Excel para no confundir "0 = pagado" con "no aplica". + 'Saldo Pendiente': cfdi.saldoPendienteMxn ?? '', + 'Estatus': cfdi.status === 'Vigente' || cfdi.status === '1' ? 'Vigente' : 'Cancelado', + 'Fecha Cancelación': cfdi.fechaCancelacion + ? new Date(cfdi.fechaCancelacion).toLocaleDateString('es-MX') + : '', + 'UUID': cfdi.uuid, + }; + + const ws = XLSX.utils.json_to_sheet([row]); + ws['!cols'] = Object.keys(row).map((key) => ({ + wch: Math.max(key.length, String(row[key as keyof typeof row] ?? '').length), + })); + + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, 'CFDI'); + + const buffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + + const idParte = [cfdi.serie, cfdi.folio].filter(Boolean).join('-') || cfdi.uuid.slice(0, 8); + saveAs(blob, `cfdi_${idParte}.xlsx`); + }; + + const selectEmisor = (emisor: EmisorReceptor) => { + setColumnFilters(prev => ({ ...prev, emisor: emisor.nombre })); + setEmisorSuggestions([]); + }; + + const selectReceptor = (receptor: EmisorReceptor) => { + setColumnFilters(prev => ({ ...prev, receptor: receptor.nombre })); + setReceptorSuggestions([]); + }; + + const applyDateFilter = () => { + setFilters({ + ...filters, + fechaInicio: columnFilters.fechaInicio || undefined, + fechaFin: columnFilters.fechaFin || undefined, + page: 1, + }); + setOpenFilter(null); + }; + + const applyEmisorFilter = () => { + setFilters({ + ...filters, + emisor: columnFilters.emisor || undefined, + page: 1, + }); + setOpenFilter(null); + }; + + const applyReceptorFilter = () => { + setFilters({ + ...filters, + receptor: columnFilters.receptor || undefined, + page: 1, + }); + setOpenFilter(null); + }; + + const clearDateFilter = () => { + setColumnFilters({ ...columnFilters, fechaInicio: '', fechaFin: '' }); + setFilters({ ...filters, fechaInicio: undefined, fechaFin: undefined, page: 1 }); + setOpenFilter(null); + }; + + const clearEmisorFilter = () => { + setColumnFilters({ ...columnFilters, emisor: '' }); + setFilters({ ...filters, emisor: undefined, page: 1 }); + setOpenFilter(null); + }; + + const clearReceptorFilter = () => { + setColumnFilters({ ...columnFilters, receptor: '' }); + setFilters({ ...filters, receptor: undefined, page: 1 }); + setOpenFilter(null); + }; + + const hasDateFilter = filters.fechaInicio || filters.fechaFin; + const hasEmisorFilter = filters.emisor; + const hasReceptorFilter = filters.receptor; + const hasActiveColumnFilters = hasDateFilter || hasEmisorFilter || hasReceptorFilter; + + const handleFilterType = (tipo?: TipoCfdi) => { + setFilters({ ...filters, tipo, page: 1 }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await createCfdi.mutateAsync(formData); + setFormData(initialFormData); + setShowForm(false); + } catch (error: any) { + alert(error.response?.data?.message || 'Error al crear CFDI'); + } + }; + + const handleBulkSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setJsonUploading(true); + try { + const cfdis = JSON.parse(bulkData); + if (!Array.isArray(cfdis)) { + throw new Error('El formato debe ser un array de CFDIs'); + } + const result = await createManyCfdis(cfdis); + alert(`Se crearon ${result.inserted} CFDIs exitosamente`); + setBulkData(''); + setShowBulkForm(false); + queryClient.invalidateQueries({ queryKey: ['cfdis'] }); + } catch (error: any) { + alert(error.message || 'Error al procesar CFDIs'); + } finally { + setJsonUploading(false); + } + }; + + // Optimized: Parse files in chunks to prevent memory issues + const handleXmlFilesChange = useCallback(async (e: React.ChangeEvent) => { + const files = Array.from(e.target.files || []); + if (files.length === 0) return; + + uploadAbortRef.current = false; + setUploadProgress({ + status: 'parsing', + totalFiles: files.length, + parsedFiles: 0, + validFiles: 0, + currentBatch: 0, + totalBatches: 0, + uploaded: 0, + duplicates: 0, + errors: 0, + errorMessages: [] + }); + setParsedCfdis([]); + + const validCfdis: CreateCfdiData[] = []; + let parsedCount = 0; + let errorCount = 0; + + // Process in chunks to prevent memory issues + for (let i = 0; i < files.length; i += PARSE_CHUNK_SIZE) { + if (uploadAbortRef.current) break; + + const chunk = files.slice(i, i + PARSE_CHUNK_SIZE); + + // Parse chunk in parallel + const results = await Promise.all( + chunk.map(async (file) => { + try { + const text = await file.text(); + const data = parseCfdiXml(text, tenantRfc); + return data; + } catch { + return null; + } + }) + ); + + // Collect valid results + results.forEach((data) => { + if (data && data.uuid) { + validCfdis.push(data); + } else { + errorCount++; + } + }); + + parsedCount += chunk.length; + + setUploadProgress(prev => ({ + ...prev, + parsedFiles: parsedCount, + validFiles: validCfdis.length, + errors: errorCount + })); + + // Small delay to allow UI to update + await new Promise(r => setTimeout(r, 10)); + } + + setParsedCfdis(validCfdis); + setUploadProgress(prev => ({ + ...prev, + status: uploadAbortRef.current ? 'idle' : 'idle', + totalBatches: Math.ceil(validCfdis.length / UPLOAD_CHUNK_SIZE) + })); + }, [tenantRfc]); + + // Optimized: Upload in batches with progress + const handleXmlBulkSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (parsedCfdis.length === 0) { + alert('No hay CFDIs validos para cargar'); + return; + } + + uploadAbortRef.current = false; + const totalBatches = Math.ceil(parsedCfdis.length / UPLOAD_CHUNK_SIZE); + + setUploadProgress(prev => ({ + ...prev, + status: 'uploading', + currentBatch: 0, + totalBatches, + uploaded: 0, + duplicates: 0, + errors: 0, + errorMessages: [] + })); + + let totalUploaded = 0; + let totalDuplicates = 0; + let totalErrors = 0; + const allErrors: string[] = []; + + // Upload in batches + for (let i = 0; i < parsedCfdis.length; i += UPLOAD_CHUNK_SIZE) { + if (uploadAbortRef.current) break; + + const batchNumber = Math.floor(i / UPLOAD_CHUNK_SIZE) + 1; + const chunk = parsedCfdis.slice(i, i + UPLOAD_CHUNK_SIZE); + + setUploadProgress(prev => ({ + ...prev, + currentBatch: batchNumber + })); + + try { + const result = await createManyCfdis(chunk, batchNumber, totalBatches, parsedCfdis.length); + + totalUploaded += result.inserted; + totalDuplicates += result.duplicates; + totalErrors += result.errors; + if (result.errorMessages) { + allErrors.push(...result.errorMessages); + } + + setUploadProgress(prev => ({ + ...prev, + uploaded: totalUploaded, + duplicates: totalDuplicates, + errors: prev.errors + result.errors, + errorMessages: allErrors.slice(0, 20) // Limit error messages + })); + } catch (error: any) { + console.error(`Error en lote ${batchNumber}:`, error); + totalErrors += chunk.length; + allErrors.push(`Lote ${batchNumber}: ${error.message || 'Error desconocido'}`); + + setUploadProgress(prev => ({ + ...prev, + errors: prev.errors + chunk.length, + errorMessages: allErrors.slice(0, 20) + })); + } + + // Small delay between batches + await new Promise(r => setTimeout(r, 100)); + } + + setUploadProgress(prev => ({ + ...prev, + status: 'complete' + })); + + // Invalidate queries to refresh the list + queryClient.invalidateQueries({ queryKey: ['cfdis'] }); + }; + + const clearXmlFiles = () => { + uploadAbortRef.current = true; + setParsedCfdis([]); + setUploadProgress({ + status: 'idle', + totalFiles: 0, + parsedFiles: 0, + validFiles: 0, + currentBatch: 0, + totalBatches: 0, + uploaded: 0, + duplicates: 0, + errors: 0, + errorMessages: [] + }); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + // Keyboard shortcuts - Esc to close popovers and forms + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + // Close open filter popovers + if (openFilter !== null) { + setOpenFilter(null); + return; + } + // Close forms + if (showForm) { + setShowForm(false); + return; + } + if (showBulkForm) { + setShowBulkForm(false); + clearXmlFiles(); + return; + } + } + }; + + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [openFilter, showForm, showBulkForm]); + + const cancelUpload = () => { + uploadAbortRef.current = true; + setUploadProgress(prev => ({ ...prev, status: 'idle' })); + }; + + const handleDelete = async (id: string) => { + if (confirm('¿Eliminar este CFDI?')) { + try { + await deleteCfdi.mutateAsync(id); + } catch (error) { + console.error('Error deleting CFDI:', error); + } + } + }; + + const openCancelDialog = (cfdi: any) => { + setCancelTarget(cfdi); + setCancelMotive('02'); + setCancelSubstitution(''); + }; + + const handleCancelFactura = async () => { + if (!cancelTarget) return; + if (cancelMotive === '01' && cancelSubstitution.trim().length !== 36) { + alert('El motivo 01 requiere el UUID completo (36 caracteres) de la factura que sustituye a esta.'); + return; + } + setCancelling(true); + try { + await cancelarFactura(cancelTarget.uuid, cancelMotive, cancelMotive === '01' ? cancelSubstitution.trim() : undefined); + await queryClient.invalidateQueries({ queryKey: ['cfdis'] }); + setCancelTarget(null); + alert('Factura cancelada. El estatus final depende del SAT (puede quedar en "pendiente" si requiere aceptación del receptor).'); + } catch (err: any) { + alert(err?.response?.data?.message || err?.message || 'Error al cancelar la factura'); + } finally { + setCancelling(false); + } + }; + + const calculateTotal = () => { + const subtotal = formData.subtotal || 0; + const descuento = formData.descuento || 0; + const iva = formData.ivaTrasladoTraslado || 0; + const isrRetencion = formData.isrRetencion || 0; + const ivaRetencion = formData.ivaTrasladoRetencion || 0; + return subtotal - descuento + iva - isrRetencion - ivaRetencion; + }; + + const formatCurrency = (value: number) => + new Intl.NumberFormat('es-MX', { + style: 'currency', + currency: 'MXN', + }).format(value); + + const formatDate = (dateString: string) => + new Date(dateString).toLocaleDateString('es-MX', { + day: '2-digit', + month: 'short', + year: 'numeric', + }); + + const generateUUID = () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }).toUpperCase(); + }; + + return ( + <> +
+
+ {/* Filters */} + + +
+
+ setSearchTerm(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + /> + +
+
+ + + +
+
+ +
+
+ + {canEdit && ( + <> + + + + )} +
+
+
+
+ + {/* Add CFDI Form */} + {showForm && canEdit && ( + + +
+
+ Agregar CFDI + Ingresa los datos del comprobante fiscal +
+ +
+
+ +
+
+
+ +
+ setFormData({ ...formData, uuid: e.target.value.toUpperCase() })} + placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" + required + /> + +
+
+
+ + +
+
+
+ + setFormData({ ...formData, serie: e.target.value })} + placeholder="A" + /> +
+
+ + setFormData({ ...formData, folio: e.target.value })} + placeholder="001" + /> +
+
+
+ +
+
+ + setFormData({ ...formData, fechaEmision: e.target.value })} + required + /> +
+
+ + setFormData({ ...formData, moneda: e.target.value.toUpperCase() })} + placeholder="MXN" + /> +
+
+ +
+
+

Emisor

+
+ + setFormData({ ...formData, rfcEmisor: e.target.value.toUpperCase() })} + placeholder="XAXX010101000" + maxLength={13} + required + /> +
+
+ + setFormData({ ...formData, nombreEmisor: e.target.value })} + placeholder="Empresa Emisora SA de CV" + required + /> +
+
+
+

Receptor

+
+ + setFormData({ ...formData, rfcReceptor: e.target.value.toUpperCase() })} + placeholder="XAXX010101000" + maxLength={13} + required + /> +
+
+ + setFormData({ ...formData, nombreReceptor: e.target.value })} + placeholder="Empresa Receptora SA de CV" + required + /> +
+
+
+ +
+
+ + setFormData({ ...formData, subtotal: parseFloat(e.target.value) || 0 })} + required + /> +
+
+ + setFormData({ ...formData, descuento: parseFloat(e.target.value) || 0 })} + /> +
+
+ + setFormData({ ...formData, ivaTraslado: parseFloat(e.target.value) || 0 })} + /> +
+
+ + setFormData({ ...formData, isrRetencion: parseFloat(e.target.value) || 0 })} + /> +
+
+ + setFormData({ ...formData, ivaRetencion: parseFloat(e.target.value) || 0 })} + /> +
+
+ + setFormData({ ...formData, total: parseFloat(e.target.value) || 0 })} + required + /> +
+
+ +
+ + +
+
+
+
+ )} + + {/* Bulk Upload Form */} + {showBulkForm && canEdit && ( + + +
+
+ Carga Masiva de CFDIs + Sube archivos XML o pega datos en formato JSON +
+ +
+
+ + {/* Mode selector */} +
+ + +
+ + {uploadMode === 'xml' ? ( +
+ {/* File input - only show when idle */} + {uploadProgress.status === 'idle' && ( +
+ +
+ + +
+
+ )} + + {/* Parsing progress */} + {uploadProgress.status === 'parsing' && ( +
+
+ +
+

Analizando archivos XML...

+

+ {uploadProgress.parsedFiles.toLocaleString()} de {uploadProgress.totalFiles.toLocaleString()} archivos +

+
+ +
+
+
+
+
+ {uploadProgress.validFiles.toLocaleString()} validos + {Math.round((uploadProgress.parsedFiles / uploadProgress.totalFiles) * 100)}% +
+
+ )} + + {/* Upload progress */} + {uploadProgress.status === 'uploading' && ( +
+
+ +
+

Subiendo CFDIs al servidor...

+

+ Lote {uploadProgress.currentBatch} de {uploadProgress.totalBatches} +

+
+ +
+
+
+
+
+
+

{uploadProgress.uploaded.toLocaleString()}

+

Cargados

+
+
+

{uploadProgress.duplicates.toLocaleString()}

+

Duplicados

+
+
+

{uploadProgress.errors.toLocaleString()}

+

Errores

+
+
+
+ )} + + {/* Upload complete */} + {uploadProgress.status === 'complete' && ( +
+
+ +
+

Carga completada

+

+ Se procesaron {uploadProgress.validFiles.toLocaleString()} archivos +

+
+
+
+
+

{uploadProgress.uploaded.toLocaleString()}

+

Cargados

+
+
+

{uploadProgress.duplicates.toLocaleString()}

+

Duplicados

+
+
+

{uploadProgress.errors.toLocaleString()}

+

Errores

+
+
+ {uploadProgress.errorMessages.length > 0 && ( +
+

Errores:

+
    + {uploadProgress.errorMessages.map((err, i) => ( +
  • {err}
  • + ))} +
+
+ )} +
+ + +
+
+ )} + + {/* Ready to upload - show summary and upload button */} + {uploadProgress.status === 'idle' && parsedCfdis.length > 0 && ( +
+
+
+
+

{parsedCfdis.length.toLocaleString()} CFDIs listos para cargar

+

+ Se enviaran en {Math.ceil(parsedCfdis.length / UPLOAD_CHUNK_SIZE)} lotes de {UPLOAD_CHUNK_SIZE} registros +

+
+ +
+
+
+ + +
+
+ )} + + {/* Initial state - no files */} + {uploadProgress.status === 'idle' && parsedCfdis.length === 0 && uploadProgress.totalFiles === 0 && ( +
+ +
+ )} + + ) : ( +
+
+ +